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

International Winter School on Software Engineering - Kotlin Basics

Anton Arhipov
February 15, 2022

International Winter School on Software Engineering - Kotlin Basics

Anton Arhipov

February 15, 2022
Tweet

More Decks by Anton Arhipov

Other Decks in Technology

Transcript

  1. Kotlin Anton Arhipov @antonarhipov Developer Advocate, JetBrains Introduction to Kotlin

    International Winter School on Software Engineering @antonarhipov
  2. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language 2014 - Kotlin 1.0 2013 - Kotlin/JS
  3. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language 2016 - Kotlin 1.0 2017 - Kotlin/Native preview 2016 - Experimental coroutines (Kotlin 1.1) 2013 - Kotlin/JS
  4. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language 2016 - Kotlin 1.0 2017 - Kotlin/Native preview 2016 - Experimental coroutines (Kotlin 1.1) 2018 - Multiplatform projects - experimental (Kotlin 1.2) 2019 - Main programming language for Android 2013 - Kotlin/JS 2018 - Coroutines release (Kotlin 1.3)
  5. Kotlin’s (very simplified) history in one slide 2011 - announced

    as an alternative JVM language 2016 - Kotlin 1.0 Current version: 1.6.20 2017 - Kotlin/Native preview 2021 - started with Kotlin/WASM 2016 - Experimental coroutines (Kotlin 1.1) 2018 - Multiplatform projects - experimental (Kotlin 1.2) 2019 - Main programming language for Android 2013 - Kotlin/JS 2018 - Coroutines release (Kotlin 1.3)
  6. // entry point fun main(args: Array<String>) { println("Hello World!") }

    Program entry point // alternatively fun main() { println("Hello World!") }
  7. Variables fun someFunction() { val a: Int = 1 //

    immediate assignment val b = 2 // `Int` type is inferred val c: Int // Type required when no initializer is provided c = 3 // deferred assignment // .. . }
  8. Variables val PI = 3.14 var x = 0 fun

    incrementX() { x += 1 }
  9. Variables val PI: Double = 3.14 var x: Int =

    0 val s: String = "Hello" Double PI = 3.14; Integer x = 0; String s = "Hello"; Explicit types Explicit types
  10. Variables val PI: Double = 3.14 var x: Int =

    0 val s: String = "Hello" val PI = 3.14 var x = 0 val s = "Hello" Double PI = 3.14; Integer x = 0; String s = "Hello"; Explicit types Type inference final var PI = 3.14; var x = 0; final var s = "Hello"; Type inference (local variables) Explicit types
  11. Functions fun sum(a: Int, b: Int): Int { return a

    + b } // expression body fun sum(a: Int, b: Int): Int = a + b
  12. Functions fun sum(a: Int, b: Int): Int { return a

    + b } // expression body fun sum(a: Int, b: Int): Int = a + b // type inference fun sum(a: Int, b: Int) = a + b
  13. Functions // return no value fun printSum(a: Int, b: Int):

    Unit { println("sum of $a and $b is ${a + b}") }
  14. Functions // return no value fun printSum(a: Int, b: Int):

    Unit { println("sum of $a and $b is ${a + b}") } // Unit can be omitted fun printSum(a: Int, b: Int) { println("sum of $a and $b is ${a + b}") }
  15. Functions as parameters fun main(args: Array<String>) { process { s

    -> println("Hello, $s!") } } fun process(operation: (String) -> Unit) { operation("World") }
  16. Lambda expressions val sum: (Int, Int) -> Int = {

    x: Int, y: Int - > x + y } val sum = { x: Int, y: Int -> x + y }
  17. Lambda expressions val sum: (Int, Int) -> Int = {

    x: Int, y: Int - > x + y } val sum = { x: Int, y: Int -> x + y } typealias IntAdder = (Int, Int) -> Int val sum: IntAdder = { x: Int, y: Int -> x + y }
  18. Default values fun find(name: String){ find(name, true) } fun find(name:

    String, recursive: Boolean){ } Function overloading
  19. Default values fun find(name: String){ find(name, true) } fun find(name:

    String, recursive: Boolean = true){ } Default value
  20. Default values fun find(name: String){ find(name, true) } fun find(name:

    String, recursive: Boolean = true){ } fun main(args: Array<String>) { find("myfile.txt") }
  21. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, color: Color = Color.BLACK, description: String = "This is a 3d figure", ) Default values + named arguments
  22. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, color: Color = Color.BLACK, description: String = "This is a 3d figure", ) Default values + named arguments Figure(Color.RED, "Red figure")
  23. class Figure( val width: Int = 1, val height: Int

    = 1, val depth: Int = 1, color: Color = Color.BLACK, description: String = "This is a 3d figure", ) Default values + named arguments Figure(color = Color.RED, description = "Red figure")
  24. Default values + named arguments Default argument values diminish the

    need for overloading in most cases. Named parameters is a necessary tool for working with default argument values
  25. Operator overloading To implement an operator, provide a member function

    or an extension function with a specific name for the corresponding type.
  26. Operator overloading To implement an operator, provide a member function

    or an extension function with a specific name for the corresponding type. data class Point(val x: Int, val y: Int) operator fun Point.unaryMinus() = Point(-x, -y) val point = Point(10, 20) fun main() { println(-point) // prints "Point(x=-10, y=-20)" }
  27. Expressions fun adjustSpeed(weather: Weather): Drive { val result: Drive if

    (weather is Rainy) { result = Safe } else { result = Calm } return result }
  28. Expressions fun adjustSpeed(weather: Weather): Drive { val result: Drive if

    (weather is Rainy) { result = Safe } else { result = Calm } return result } fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe else Calm Note to myself: show in the IDE
  29. Expressions fun adjustSpeed(weather: Weather) : Drive = ... private fun

    adjustSpeed(weather: Weather) = For public API, keep the return type in the signature For private API it is generally OK to use type inference
  30. Expressions The expressions if, try, and when can return values

    as a result Note to myself: show in the IDE
  31. fun adjustSpeed(weather: Weather): Drive = when (weather) { is Rainy

    -> Safe else - > Calm } ‘when’ expression is exhaustive for sealed classes, enums and booleans
  32. fun adjustSpeed(weather: Weather): Drive = when (weather) { is Rainy

    -> Safe // else -> Calm } Compilation error: We should either specify all options or use the ‘else’ branch sealed class Weather object Rainy : Weather() object Sunny : Weather()
  33. Ranges val x = 10 val y = 9 if

    (x in 1 .. y+1) { println("fits in range") }
  34. Ranges: iteration for (x in 1 . . 5) {

    print(x) } for (x in 1 . . 10 step 2) { print(x) } for (x in 9 downTo 0 step 3) { print(x) }
  35. fun isLatinUppercase(c: Char) = c >= 'A' & & c

    < = 'Z' Use range checks instead of comparison pairs
  36. fun isLatinUppercase(c: Char) = c >= 'A' & & c

    < = 'Z' fun isLatinUppercase(c: Char) = c in 'A' .. 'Z' Use range checks instead of comparison pairs
  37. class Version(val major: Int, val minor: Int): Comparable<Version> { override

    fun compareTo(other: Version): Int { if (this.major ! = other.major) { return this.major - other.major } return this.minor - other.minor } } fun main() { val versionRange = Version(1, 11) . . Version(1, 30) println(Version(0, 9) in versionRange) println(Version(1, 20) in versionRange) } Comparable range
  38. class Version(val major: Int, val minor: Int): Comparable<Version> { override

    fun compareTo(other: Version): Int { if (this.major ! = other.major) { return this.major - other.major } return this.minor - other.minor } } fun main() { val versionRange = Version(1, 11) . . Version(1, 30) println(Version(0, 9) in versionRange) println(Version(1, 20) in versionRange) } Comparable range public operator fun <T : Comparable<T > > T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)
  39. class Version(val major: Int, val minor: Int): Comparable<Version> { override

    fun compareTo(other: Version): Int { if (this.major ! = other.major) { return this.major - other.major } return this.minor - other.minor } } fun main() { val versionRange = Version(1, 11) . . Version(1, 30) println(Version(0, 9) in versionRange) println(Version(1, 20) in versionRange) } Comparable range public operator fun <T : Comparable<T > > T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)
  40. Ranges in loops fun main(args: Array<String>) { for (i in

    0 .. args.size - 1) { println("$i: ${args[i]}") } }
  41. Ranges in loops fun main(args: Array<String>) { for (i in

    0 .. args.size - 1) { println("$i: ${args[i]}") } }
  42. Ranges in loops fun main(args: Array<String>) { for (i in

    0 .. args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") }
  43. Ranges in loops fun main(args: Array<String>) { for (i in

    0 .. args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") } for (i in args.indices) { println("$i: ${args[i]}") }
  44. Ranges in loops fun main(args: Array<String>) { for (i in

    0 .. args.size - 1) { println("$i: ${args[i]}") } } for (i in 0 until args.size) { println("$i: ${args[i]}") } for (i in args.indices) { println("$i: ${args[i]}") } for ((i, value) in args.withIndex()) { println("$i: $value}") }
  45. Destructuring declarations data class Person(val firstName: String, val lastName: String)

    val p = Person("Anton", "Arhipov") val name = p.component1() val age = p.component2()
  46. Destructuring: returning multiple values data class Result(val result: Int, val

    status: Status) fun function( ... ): Result { // computations return Result(result, status) } // Now, to use this function: val (result, status) = function( .. . )
  47. Destructuring: iterating over a map for ((key, value) in map)

    { // do something with the key and the value }
  48. Null-safety class Nullable { fun someFunction(){} } fun createNullable(): Nullable?

    = null fun main() { val n: Nullable? = createNullable() n.someFunction() }
  49. Null-safety class Nullable { fun someFunction(){} } fun createNullable(): Nullable?

    = null fun main() { val n: Nullable? = createNullable() n.someFunction() }
  50. Using null-safe call val order = retrieveOrder() if (order ==

    null || order.customer = = null || order.customer.address == null){ throw IllegalArgumentException("Invalid Order") } val city = order.customer.address.city
  51. Using null-safe call val order = retrieveOrder() if (order ?.

    customer ?. address == null){ throw IllegalArgumentException("Invalid Order") } val city = order.customer.address.city
  52. Using null-safe call val order = retrieveOrder() val city =

    order ?. customer ?. address ?. city ?: throw IllegalArgumentException("Invalid Order")
  53. Using null-safe call val order = retrieveOrder() val city =

    order ! ! .customer ! ! .address ! ! .city “You may notice that the double exclamation mark looks a bit rude: it’s almost like you’re yelling at the compiler. This is intentional.” - Kotlin in Action
  54. Use lateinit instead of !! class MyTest { class State(val

    data: String) private var state: State? = null @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state ! ! .data) } }
  55. Use lateinit instead of !! class MyTest { class State(val

    data: String) private var state: State? = null @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state ! ! .data) } } class MyTest { class State(val data: String) private lateinit var state: State @BeforeEach fun setup() { state = State("abc") } @Test fun foo() { assertEquals("abc", state.data) } }
  56. class Person(val name: String?, val age: Int?) val p =

    retrievePerson() ?: Person() Use elvis operator
  57. class Person(val name: String?, val age: Int?) val p =

    retrievePerson() ?: Person() Use elvis operator
  58. Use elvis operator as return and throw class Person(val name:

    String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age == null) return println("$name: $age") }
  59. Use elvis operator as return and throw class Person(val name:

    String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age == null) return println("$name: $age") }
  60. Use elvis operator as return and throw class Person(val name:

    String?, val age: Int?) fun processPerson(person: Person) { val name = person.name if (name = = null) throw IllegalArgumentException("Named required") val age = person.age if (age == null) return println("$name: $age") }
  61. Use elvis operator as return and throw class Person(val name:

    String?, val age: Int?) fun processPerson(person: Person) { val name = person.name ? : throw IllegalArgumentException("Named required") val age = person.age ?: return println("$name: $age") }
  62. Consider using safe cast for type checking override fun equals(other:

    Any?) : Boolean { val command = other as Command return command.id == id } override fun equals(other: Any?) : Boolean { return (other as? Command) ?. id == id } ‘as’ throws a ClassCastException on cast failure ‘as?’ returns null value on cast failure
  63. Idiomatic - using, containing, or denoting expressions that are natural

    to a native speaker Commonly accepted style Effective use of features
  64. val name = "Joe" val s = buildString { repeat(5)

    { append("Hello, ") append(name) appendLine("!") } } println(s) String name = "Joe"; StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i ++ ) { sb.append("Hello, "); sb.append(name); sb.append("!\n"); } System.out.println(sb); stdlib
  65. System.out.appendHTML().html { body { div { a("http: // kotlinlang.org") {

    target = ATarget.blank +"Main site" } } } } kotlinx.html
  66. kotlinx.html fun main() { embeddedServer(Netty, port = 8080, host =

    "0.0.0.0") { routing { get("/html-dsl") { call.respondHtml { body { h1 { +"HTML" } ul { for (n in 1 .. 10) { li { +"$n" } } } } } } } }.start(wait = true) }
  67. kotlinx.html fun main() { embeddedServer(Netty, port = 8080, host =

    "0.0.0.0") { routing { get("/html-dsl") { call.respondHtml { body { h1 { +"HTML" } ul { for (n in 1 .. 10) { li { +"$n" } } } } } } } }.start(wait = true) } Ktor’s routing kotlinx.html