Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Lesser-Known Kotlin Features

Anton Arhipov
September 13, 2022

The Lesser-Known Kotlin Features

The lesser-known Kotlin features

Kotlin is a modern programming language initially created as a "better Java." However, Kotlin provides not just a better syntax but a ton of new interesting features. In this session, you will learn about the subset of the lesser know features that might look obscure at first sight. How does the reified keyword in Kotlin work? What is the difference between inline and crossinline, and why is noinline required? Why do we need to indicate some types as "definitely non-nullable"? What's the deal with context receivers, and what's the lambda with the receiver? After this session, you will have an idea about the hidden Kotlin gems features and when to apply these advanced features.

Anton Arhipov

September 13, 2022
Tweet

More Decks by Anton Arhipov

Other Decks in Programming

Transcript

  1. Kotlin Features The Lesser-Known @antonarhipov

  2. @antonarhipov

  3. Why Kotlin?

  4. Null-safety Coroutines Multiplatform Syntax Why Kotlin?

  5. Null-safety Coroutines Multiplatform Syntax Why Kotlin?

  6. Null-safety Coroutines Multiplatform Syntax Why Kotlin?

  7. Null-safety Coroutines Multiplatform Syntax Why Kotlin?

  8. Null-safety Coroutines Multiplatform Syntax Why Kotlin?

  9. None
  10. Default values and named arguments

  11. class Figure( val width: Int, val height: Int, val depth:

    Int, val color: Color, val description: String, )
  12. class Figure( val width: Int, val height: Int, val depth:

    Int, val color: Color, val description: String, ) Figure(1, 2, 3, Color.RED, "Red brick") Figure(1, 1, 1, Color.GREEN, "Green cube")
  13. class Figure( val width: Int, val height: Int, val depth:

    Int, val color: Color, val description: String, ) Figure(1, 2, 3, Color.RED, "Red brick") Figure(1, 1, 1, Color.GREEN, "Green cube") Figure(1, 1, 1, Color.BLUE, "Blue cube")
  14. class Figure( val width: Int, val height: Int, val depth:

    Int, val color: Color, val description: String, ) Figure(1, 2, 3, Color.RED, "Red brick") Figure(1, 1, 1, Color.GREEN, "Green cube") Figure(1, 1, 1, Color.BLUE, "Blue cube") Figure(1, 1, 1, Color.BLACK, "Black cube")
  15. class Figure( val width: Int, val height: Int, val depth:

    Int, val color: Color, val description: String, ) Figure(1, 2, 3, Color.RED, "Red brick") Figure(1, 1, 1, Color.GREEN, "Green cube") Figure(1, 1, 1, Color.BLUE, "Blue cube") Figure(1, 1, 1, Color.BLACK, "Black cube")
  16. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, val color: Color = Color.BLACK, val description: String = "This is ${color} figure", ) Figure(1, 2, 3, Color.RED, "Red brick") Figure(1, 1, 1, Color.GREEN, "Green cube") Figure(1, 1, 1, Color.BLUE, "Blue cube") Figure(1, 1, 1, Color.BLACK, "Black cube")
  17. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, val color: Color = Color.BLACK, val description: String = "This is ${color} figure", ) Figure(1, 2, 3, Color.RED, "Red brick") Figure(Color.GREEN) Figure(Color.BLUE) Figure(Color.BLACK)
  18. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, val color: Color = Color.BLACK, val description: String = "This is ${color} figure", ) Figure(1, 2, 3, Color.RED, "Red brick") Figure(Color.GREEN) Figure(Color.BLUE) Figure(Color.BLACK) Would not compile!
  19. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, val color: Color = Color.BLACK, val description: String = "This is ${color} figure", ) Figure(1, 2, 3, Color.RED, "Red brick") Figure(color = Color.GREEN) Figure(color = Color.BLUE) Figure(color = Color.BLACK)
  20. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, val color: Color = Color.BLACK, val description: String = "This is ${color} figure", ) Figure(width = 1, height = 2, depth = 3, Color.RED, "Red brick") Figure(color = Color.GREEN) Figure(color = Color.BLUE) Figure(color = Color.BLACK)
  21. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, val color: Color = Color.BLACK, val description: String = "This is ${color} figure", ) Figure(width = 1, height = 2, depth = 3, Color.RED, "Red brick") Figure(color = Color.GREEN) Figure(color = Color.BLUE) Figure(color = Color.BLACK)
  22. Type-safe builders

  23. None
  24. foo { bar { baz = "Hello!" qux = quux

    { corge = "Blah" } } }
  25. foo { bar { baz = "Hello!" qux = quux

    { corge = "Blah" } } }
  26. foo { bar { baz = "Hello!" qux = quux

    { corge = "Blah" } } }
  27. foo { bar { baz = "Hello!" qux = quux

    { corge = "Blah" } } }
  28. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = "Blah" } } }
  29. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } }
  30. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } }
  31. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } } Named arguments
  32. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } } Higher-order functions
  33. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } } Trailing lambda
  34. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } } Extension function it: Foo fun Foo.bar(c: () -> Unit){ ... } it.
  35. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } } SAM-conversion fun quux(r: Runnable){ ... }
  36. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } } Lambda with receiver this: Foo fun foo(c: Foo.() -> Unit){ ... }
  37. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } } Lambda with receiver this: Foo fun foo(c: Foo.() -> Unit){ ... }
  38. foo { bar(grault = 1) { baz = "Hello!" qux

    = quux { corge = Blah() } } } Lambda with receiver this: Foo fun foo(c: Foo.() -> Unit){ ... } DEMO TIME?
  39. Kotlin DSL in TeamCity project { vcsRoot(ApplicationVcs) buildType { id("Application")

    name = "Application" vcs { root(ApplicationVcs) } artifactRules = "target/*jar" steps { maven { goals = "clean package" } } triggers { vcs {} } dependencies { snapshot(Library) {}
  40. +

  41. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) }

  42. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) } inline

    fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext = SpringApplication.run(T :: class.java, *args)
  43. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) } inline

    fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext = SpringApplication.run(T :: class.java, *args) inline fun <reified T : Any> runApplication(vararg args: String, init: SpringApplication.() -> Unit): ConfigurableApplicationContext = SpringApplication.run(T :: class.java).apply(init).run(*args)
  44. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) } inline

    fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext = SpringApplication.run(T :: class.java, *args) inline fun <reified T : Any> runApplication(vararg args: String, init: SpringApplication.() -> Unit): ConfigurableApplicationContext = SpringApplication.run(T :: class.java).apply(init).run(*args)
  45. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { }

    } this: SpringApplication
  46. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { addInitializers(beans)

    } } this: SpringApplication
  47. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { addInitializers(beans)

    } } val beans = beans { bean { CommandLineRunner { println("start data initialization ... ") val repository = ref<MyRepository>() repository.save(Message(text = "this is the first message!")) repository.save(Message(text = "this is the second message!")) } } } this: SpringApplication
  48. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { addInitializers(beans)

    } } val beans = beans { bean { CommandLineRunner { println("start data initialization ... ") val repository = ref<MyRepository>() repository.save(Message(text = "this is the first message!")) repository.save(Message(text = "this is the second message!")) } } } this: SpringApplication this: BeanDefinitionDsl this: BeanDefinitionDsl.BeanSupplierContext it: Array<String>
  49. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { addInitializers(beans)

    } } val beans = beans { bean { CommandLineRunner { println("start data initialization ... ") val repository = ref<MyRepository>() repository.save(Message(text = "this is the first message!")) repository.save(Message(text = "this is the second message!")) } } }
  50. T.() -> Unit

  51. Builder inference

  52. val list = buildList<String> { }

  53. val list = buildList<String> { }

  54. val list = buildList<String> { }

  55. val list = buildList<String> { } @SinceKotlin("1.6") @WasExperimental(ExperimentalStdlibApi :: class)

    @kotlin.internal.InlineOnly @Suppress("DEPRECATION") public inline fun <E> buildList(@BuilderInference builderAction: MutableList<E>.() - > Unit): List<E> contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } return buildListInternal(builderAction) }
  56. val list = buildList<String> { }

  57. val list = buildList { val x = get(0) }

  58. val list = buildList { val x = get(0) }

    Not enough type information
  59. val list = buildList { val x: Int = get(0)

    } this: MutableList<Int>
  60. val list = buildList { add("hello!") val x = get(0)

    } this: MutableList<String>
  61. val list = buildList { add("hello!") val x: Int =

    get(0) } this: MutableList<String>
  62. val list = buildList { add("hello!") val x: Int =

    get(0) } this: MutableList<String> Type mismatch
  63. val list = buildList { val x = get(0) doSomething(x)

    } this: MutableList<Int> fun doSomething(x: Int){…}
  64. None
  65. Inline

  66. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { println(it)

    } }
  67. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { println(it)

    } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { if (i % 2 == 0) { block(element) } } }
  68. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { println(it)

    } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { if (i % 2 == 0) { block(element) } } } Inlines everything!
  69. fun main() { val ints = listOf(1,2,3,4) for ((i, element)

    in withIndex()) { if (i % 2 == 0) { println(element) } } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { if (i % 2 == 0) { block(element) } } }
  70. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { println(it)

    } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() } }
  71. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { println(it)

    } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() } }
  72. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { println(it)

    } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() } }
  73. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { println(it)

    } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() } }
  74. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { if(it

    == 3) return println(it) } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() } Non-local return Returns from main
  75. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther l@{ if(it

    == 3) return@l println(it) } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() } Local return Returns from lambda
  76. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther l@{ if(it

    == 3) return@l println(it) } } inline fun Collection<Int>.forEveryOther(block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() }
  77. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther l@{ if(it

    == 3) return@l println(it) } } inline fun Collection<Int>.forEveryOther(crossinline block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() }
  78. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { if(it

    == 3) return println(it) } } inline fun Collection<Int>.forEveryOther(crossinline block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() }
  79. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther { if(it

    == 3) return println(it) } } inline fun Collection<Int>.forEveryOther(crossinline block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() }
  80. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther l@{ if(it

    == 3) return@l println(it) } } inline fun Collection<Int>.forEveryOther(crossinline block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() }
  81. fun main() { val ints = listOf(1,2,3,4) ints.forEveryOther l@{ if(it

    == 3) return@l println(it) } } inline fun Collection<Int>.forEveryOther(crossinline block: (Int) -> Unit) { for ((i, element) in withIndex()) { val task = Runnable { if (i % 2 == 0) { block(element) } } task.run() } Inlines the loop Lambda is compiled to a separate class
  82. fun main() { val ints = listOf(1,2,3,4) for ((i, element)

    in withIndex()) { val task: Runnable = (Runnable)Kt$inlined$forEveryOther(i, element) task.run() } } class Kt$inlined$forEveryOther implements Runnable { . .. public final run(){ if (i % 2 == 0) { if(element ! = 3) System.out.println(element) } } }
  83. Noinline

  84. inline fun Collection<Int>.forEveryOther(blockA: (Int) -> Unit, blockB: (Int) -> Unit,

    ) { ... doSomething(blockB) }
  85. inline fun Collection<Int>.forEveryOther(blockA: (Int) -> Unit, blockB: (Int) -> Unit,

    ) { ... doSomething(blockB) } fun doSomething(block: (Int) -> Unit) { ... }
  86. inline fun Collection<Int>.forEveryOther(blockA: (Int) -> Unit, blockB: (Int) -> Unit,

    ) { ... doSomething(blockB) } fun doSomething(block: (Int) -> Unit) { ... }
  87. inline fun Collection<Int>.forEveryOther(blockA: (Int) -> Unit, noinline blockB: (Int) ->

    Unit, ) { ... doSomething(blockB) } fun doSomething(block: (Int) -> Unit) { ... }
  88. inline fun Collection<Int>.forEveryOther(blockA: (Int) -> Unit, noinline blockB: (Int) ->

    Unit, ) { ... doSomething(blockB) } fun doSomething(block: (Int) -> Unit) { ... }
  89. (Rei fi ed) Generics

  90. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) } inline

    fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext = SpringApplication.run(T :: class.java, *args)
  91. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) } inline

    fun <reified T : Any> runApplication(vararg args: String): ConfigurableApplicationContext = SpringApplication.run(T :: class.java, *args)
  92. fun <T> printType() { println(T : : class.java) }

  93. fun <T> printType(c: Class<T>) { println(c : : class.java) }

  94. inline fun <reified T> printType() { println(T : : class.java)

    }
  95. inline fun <reified T> printType() { println(T : : class.java)

    } fun printStringType(){ printType<String>() }
  96. inline fun <reified T> printType() { println(T :: class.java) }

    fun printStringType(){ printType<String>() } public static final void printType() { int $i$f$printType = 0; Intrinsics.reifiedOperationMarker(4, "T"); Class var1 = Object.class; System.out.println(var1); } public static final void printStringType() { int $i$f$printType = false; Class var1 = String.class; System.out.println(var1); }
  97. inline fun <reified T> printType() { println(T :: class.java) }

    fun printStringType(){ printType<String>() } public static final void printType() { int $i$f$printType = 0; Intrinsics.reifiedOperationMarker(4, "T"); Class var1 = Object.class; System.out.println(var1); } public static final void printStringType() { int $i$f$printType = false; Class var1 = String.class; System.out.println(var1); }
  98. inline fun <reified T> printType() { println(T :: class.java) }

    fun printStringType(){ printType<String>() } public static final void printType() { int $i$f$printType = 0; Intrinsics.reifiedOperationMarker(4, "T"); Class var1 = Object.class; System.out.println(var1); } public static final void printStringType() { int $i$f$printType = false; Class var1 = String.class; System.out.println(var1); }
  99. inline fun <reified T> calculate(value: Float): T { return when

    (T :: class) { Int :: class -> value.toInt() as T Float :: class -> value as T else -> throw IllegalArgumentException("$value is neither Float or Int") } } val intValue: Int = calculate(123f) val floatValue: Float = calculate(123f)
  100. val list: List<Any> = listOf(1, "Hello", Color.RED) val strings: List<String>

    = list.filterIsInstance<String>()
  101. val list: List<Any> = listOf(1, "Hello", Color.RED) val strings: List<String>

    = list.filterIsInstance<String>() public inline fun <reified R> Iterable < *> .filterIsInstance(): List<R> { return filterIsInstanceTo(ArrayList<R>()) }
  102. Type-safe builders Functional literal (aka lambda) with receiver Builder inference

    Inline, crossinline, noinline (Rei fi ed) generics Some stdlib examples
  103. Type-safe builders Functional literal (aka lambda) with receiver Builder inference

    Inline, crossinline, noinline (Rei fi ed) generics Some stdlib examples
  104. Kotlin { YouTube = "youtube.com/kotlin" Slack = "slack.kotl.in" } me

    { name = "Anton Arhipov" twitter = "@antonarhipov" slides = speakerdeck.com/antonarhipov } this: Person this: Project