Skip to content

Kotlin

September 29, 2023
April 11, 2019

List and Array

Use List<T>.toTypeArray() to get primitive Java array (T[])

// range to array
// array to list

Class

https://khan.github.io/kotlin-for-python-developers/#inheritance

Kotlin classes are closed (Java final) by default, use open modifier to allow inheritance.

constructor keyword can be omitted for primary constructor unless you specify access modifiers (protected, internal, etc)or annotations.

Primary constructor can use the parameters to initialize properties of the same name with default getters and setters. Note property setter is not invoked in this process. Declare variable in class, use init{} block and set the property manually to invoke the setter. Private property is also declared this way.
Use keyword field refer to the field in accessor instead of using actual property name to avoid infinite recursion.
https://khan.github.io/kotlin-for-python-developers/#setters-and-getters

Primary constructor calls the parent contractor right away by class name. So from the superclass's perspective, calling an open function in constructor is risky: it might be overridden in the subclass.
Secondary constructor calls other constructor (if available) with this() or parent constructor with super() (if none constructor available)

Subclass may refer functions and properties of superclass via super keyword: super.foo().

Property = hidden field + default/custom accessor
Computed property without backing field can also be (declare it without initialization)
android - What's Kotlin Backing Field For? - Stack Overflow

val isNewborn
    get() = age == 0

Reflection

https://khan.github.io/kotlin-for-python-developers/#member-references-and-reflection

// getting class of object
// since 1.1
// https://kotlinlang.org/docs/reference/reflection.html#class-references
instance::class.qualifiedName

// obsolete style
instance.javaclass
instance.javaclass.kotlin

// getting name of variable
::instance.name

Smartcast

if (obj is String) {  // obj: Any
  print(obj.toUpperCase())     // obj is now known to be a String
}
// extension not using contract, no smartcast
fun String?.isNotNull(): Boolean = this != null

fun foo(s: String?) {
    if (s != null) s.length // smartcast here
    if (s.isNotNull()) s.length // No smartcast :(

    if (!s.isNullOrEmpty()) {
        println("length of '$s' is ${s.length}") // Yay, smartcasted to not-null!
    }
}

Delegates

interface View {
  fun click()
  fun toggle()
}

class ViewImpl: View {
  override fun click()  { println("click!") }
  override fun toggle() { println("toggle!") }
}

class EmptyDelegate(view: View): View by view

class OverridingDelegate(view: View): View by view {
  override fun click()  { println("CLICK!") }
}

fun main() {
   val view = ViewImpl()
   val e = EmptyDelegate(view)
   val o = OverridingDelegate(view)

   println("${e::class.qualifiedName}")
   e.click()
   e.toggle()

   println("${o::class.qualifiedName}")
   o.click()
   o.toggle()
}

Label

continue, break, this, return in bound to the closest context. Use label (name@) to specify the context.
Function and class name are automatically generated labels.

outer@ for (n in 2..100) {
    for (d in 2 until n) {
        if (n % d == 0) continue@outer
    }
    println("$n is prime")
}
fun printUntilStopExplicit() {
  val list = listOf("a", "b", "stop", "c")
  list.forEach stop@ {
    if (it == "stop") return@stop
    else println(it)
  }
}

fun printUntilStopImplicit() {
  val list = listOf("a", "b", "stop", "c")
  list.forEach {
    if (it == "stop") return@forEach
    else println(it)
  }
}
class A {
  private val somefield: Int = 1
  inner class B {
    private val somefield: Int = 1
    fun foo(s: String) {
      println("Field <somefield> from B" + this.somefield)
      println("Field <somefield> from B" + this@B.somefield)
      println("Field <somefield> from A" + this@A.somefield)
    }
  }
}

Range

class DateRange(val start: MyDate, val endInclusive: MyDate) {
    operator fun contains(item: MyDate): Boolean =
      start <= item && item <= endInclusive
}

Ranges - Kotlin Programming Language

Spread and Destructuring

Spread (*list) and destructuring ((k, v) = "key" to "value") works as in Python and ES6

Map (dict) spreading is more tricky. The parameters have to be of the same type vararg kwargs: Pair<String, X> or in the worst cast vararg kwargs: Pair<String, Any> and it's for the function to typecast them to the correct type.

val numbers = listOf(1, 2, 3)
sum(*numbers.toIntArray())

foo("bar" to 42, "test" to "hello")

// destructuring example

Lambda

https://khan.github.io/kotlin-for-python-developers/#receivers

fun buildString(action: StringBuilder.() -> Unit) : String {
  val sb = StringBuilder()
  sb.action()  // invoke the lambda as StringBuilder's extension function
  return sb.toString()
}

// inside the lambda, `this` refer to the StringBuilder object
val s = buildString {
  // this lambda has prototype `() -> Unit`
  append("Hello!")
  append("How are you?")
}

Single Abstract Method (SAM)

//https://play.kotlinlang.org/koans/Introduction/SAM%20conversions/Task.kt

import java.util.*

fun getList(): List<Int> {
    val arrayList = arrayListOf(1, 5, 2)
    // Collections.sort(arrayList, object: Comparator<Int> {
    //     override fun compare(a:Int, b:Int) = b - a
    // })
    // use lambda SAM instead of anonymous object
    Collections.sort(arrayList, { x, y -> y - x })
    return arrayList
}
https://play.kotlinlang.org/koans/Introduction/Extensions%20on%20collections/Task.kt

fun getList(): List<Int> {
    val a = arrayListOf(1, 5, 2)
    a.sortWith(object: Comparator<Int> {
        override fun compare(x:Int, y:Int) = y - x
    })
    // lambda also works but you have to specify the
    // `Comparator` type (template type is inferred)
    a.sortWith(Comparator { x, y -> y - x })
    return a
}

Why Can't Kotlin Infer The Type For Comparator - Stack Overflow
SAM for Kotlin classes : KT-7770

Extension

Syntax:

fun {Class}.{name}({param}): {type} {

}

The class specified is the "receiver" of extension. Use this in the function body to refer to the receiver object instance. this can be omitted if there is no ambiguity, i.e.: the receiver object is the context of the code block.
https://khan.github.io/kotlin-for-python-developers/#extension-functionsproperties
https://khan.github.io/kotlin-for-python-developers/#receivers
[Kotlin pearls 6] Extensions: The Good, The Bad and The Ugly

// extension function
fun Byte.toUnsigned(): Int {
    return if (this < 0) this + 256 else this.toInt()
}

// extension property
val Byte.unsigned: Int
    get() = if (this < 0) this + 256 else this.toInt()

Context functions

Personally I prefer not using receiver (not using this).

maybeNull?.run { /* receiver, use `this` */ }
maybeNull?.let { /* parameter, use `it` */ }
with(expression) { /* receiver, use `this` */ }

maybeNull?.apply {
  /* receiver, use `this` */
}?.memberFunction()

maybeNull?.also {
  /* parameter, use `it` */
}?.memberFunction()

DSL

Type-Safe Builders - Kotlin Programming Language

Tree DSL

class TreeNode(val name: String) {
    val children = mutableListOf<TreeNode>()

    fun node(name: String, initialize: (TreeNode.() -> Unit)? = null) {
        val child = TreeNode(name)
        children.add(child)
        if (initialize != null) {
            child.initialize()
        }
    }

    fun toString() {
      TODO()
    }
}

fun tree(name: String, initialize: (TreeNode.() -> Unit)? = null): TreeNode {
    val root = TreeNode(name)
    if (initialize != null) {
        root.initialize()
    }
    return root
}

val t = tree("root") {
    node("math") {
        node("algebra")
        node("trigonometry")
    }
    node("science") {
        node("physics")
    }
}