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. class Figure( val width: Int, val height: Int, val depth:

    Int, val color: Color, val description: String, )
  2. 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")
  3. 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")
  4. 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")
  5. 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")
  6. 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")
  7. 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)
  8. 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!
  9. 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)
  10. 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)
  11. 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)
  12. foo { bar { baz = "Hello!" qux = quux

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    = quux { corge = Blah() } } } Lambda with receiver this: Foo fun foo(c: Foo.() -> Unit){ ... } DEMO TIME?
  27. 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) {}
  28. +

  29. @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)
  30. @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)
  31. @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)
  32. @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
  33. @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>
  34. @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!")) } } }
  35. 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) }
  36. val list = buildList { val x = get(0) }

    Not enough type information
  37. val list = buildList { add("hello!") val x: Int =

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

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

    } this: MutableList<Int> fun doSomething(x: Int){…}
  40. 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) } } }
  41. 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!
  42. 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) } } }
  43. 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() } }
  44. 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() } }
  45. 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() } }
  46. 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() } }
  47. 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
  48. 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
  49. 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() }
  50. 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() }
  51. 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() }
  52. 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() }
  53. 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() }
  54. 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
  55. 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) } } }
  56. inline fun Collection<Int>.forEveryOther(blockA: (Int) -> Unit, blockB: (Int) -> Unit,

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

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

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

    Unit, ) { ... doSomething(blockB) } fun doSomething(block: (Int) -> Unit) { ... }
  60. @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)
  61. @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)
  62. inline fun <reified T> printType() { println(T : : class.java)

    } fun printStringType(){ printType<String>() }
  63. 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); }
  64. 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); }
  65. 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); }
  66. 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)
  67. 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>()) }
  68. Type-safe builders Functional literal (aka lambda) with receiver Builder inference

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

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

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