Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Koans

Classes

  • Classes in Kotlin are declared using the keyword class:
    class Person { /*...*/ }
    
  • Can have a primary constructor and one or more secondary constructors
    class Person constructor(firstName: String) { /*...*/ }
    
  • If the primary constructor does not have any annotations or visibility modifiers, the constructor keyword can be omitted:
    class Person(firstName: String) { /*...*/ }
    
  • Kotlin has a concise syntax for declaring properties and initializing them from the primary constructor:
    class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true)
    

    Much like regular properties, properties declared in the primary constructor can be mutable (var) or read-only (val).

Data Classes

Classes whose main purpose is to hold data.
In such classes, some standard functionality and some utility functions are often mechanically derivable from the data.
In Kotlin, these are called data classes and are marked with data:

data class User(val name: String, val age: Int)

The compiler automatically derives the following members from all properties declared in the primary constructor:

  • equals()/ hashCode() pair
  • toString() of the form User(name=John, age=42)
  • componentN() functions corresponding to the properties in their order of declaration
  • copy() function

Prerequisites

  • The primary constructor needs to have at least 1 parameter.
  • All primary constructor parameters need to be marked as val or var.
  • Data classes cannot be abstract, open, sealed, or inner.

Copy()

Use the copy() function to copy an object allowing to alter some of its properties while keeping the rest unchanged

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

Destructuring

Component functions generated for data classes make it possible to use them in destructuring declarations:

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"

Exercise

  • Migrate this java code to a data class in Kotlin
  • Instantiate 2 persons : “Neo”, “Trinity”
  • Use the copy function to create a new Person called “NeoV2” based on
    public class Person {
      private final String name;
      private final int age;
    
      public Person(String name, int age) {
          this.name = name;
          this.age = age;
      }
    
      public String getName() {
          return name;
      }
    
      public int getAge() {
          return age;
      }
    }
    

Objects

The Singleton pattern can be useful in several cases, and Kotlin makes it easy to declare singletons:

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

Companion Objects

An object declaration inside a class can be marked with the companion keyword:

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
    
    // The name of the companion object can be omitted
    companion object { }
}
  • A good way to define new more explicit types with enforced rules
  • Makes it impossible to represent invalid states

Exercise

  • Create a NonZeroPositiveInteger class with its companion
  • We use the companion to create NonZeroPositiveIntegers
  • The companion should contain 2 functions :
    fun from(value: Int): NonZeroPositiveInteger = { ... }
    fun toInt(i: NonZeroPositiveInteger): Int = { ... }
    

Inline classes

Sometimes it is necessary for business logic to create a wrapper around some type. Kotlin introduces a special kind of class called inline class. They don’t have an identity and can only hold values.

To declare an inline class for the JVM backend, use the value modifier along with the @JvmInline annotation before the class declaration:

// For JVM backends
@JvmInline
value class Password(private val s: String)

An inline class must have a single property initialized in the primary constructor. At runtime, instances of the inline class will be represented using this single property

You have 2 examples of it in the koans-solution.ws.kts file

Extension functions

Kotlin provides the ability to extend a class with new functionality without having to inherit from the class or use design patterns such as Decorator.

For example, you can write new functions for a class from a third-party library that you can’t modify

  • To declare an extension function :
    • prefix its name with a receiver type, which refers to the type being extended
      fun MutableList<Int>.swap(index1: Int, index2: Int) {
        val tmp = this[index1] // 'this' corresponds to the list
        this[index1] = this[index2]
        this[index2] = tmp
      }
      

      The this keyword inside an extension function corresponds to the receiver object (the one that is passed before the dot). Now, you can call such a function on any MutableList:

Companion object extensions

If a class has a companion object defined, you can also define extension functions and properties for the companion object. Just like regular members of the companion object, they can be called using only the class name as the qualifier:

class MyClass {
    companion object { }  // will be called "Companion"
}
fun MyClass.Companion.printCompanion() { println("companion") }

fun main() {
    MyClass.printCompanion()
}

Let : scope functions

Several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope.

  • In this scope, you can access the object without its name.
  • Such functions are called scope functions.
  • There are five of them: let, run, with, apply, and also.
// Without let :
val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

// With let
Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}
  • let scope invoked only when not null
val listWithNulls: List<String?> = listOf("Kotlin", null, "Clojure", null, "Scala")
for (item in listWithNulls) {
    item?.let { println(it) }
}

Higher-oder functions

A higher-order function is a function that takes functions as parameters, or returns a function

fun <T, R> Collection<T>.fold(
    initial: R,
    // accepts any function matching (R, T) -> R
    // Taking an R and a T as parameters and returning an R
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}

Inline functions

Using higher-order functions imposes certain runtime penalties:

  • each function is an object
  • it captures a closure

A closure is a scope of variables that can be accessed in the body of the function.

Every time we declare a higher-order function, at least one instance of those special Function* types will be created.
The extra memory allocations get even worse when a lambda captures a variable: The JVM creates a function type instance on every invocation.

When using inline functions, the compiler inlines the function body.
It substitutes the body directly into places where the function gets called.

By default, the compiler inlines the code for both the function itself and the lambdas passed to it.

inline fun <T> Collection<T>.each(block: (T) -> Unit) {
    for (e in this) block(e)
}

For example, The compiler will translate :

val numbers = listOf(1, 2, 3, 4, 5)
numbers.each { println(it) }

To something like:

val numbers = listOf(1, 2, 3, 4, 5)
for (number in numbers)
    println(number)

When using inline functions, there is no extra object allocation and no extra virtual method calls.