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

Idiomatic Kotlin, 2nd edition

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Idiomatic Kotlin, 2nd edition

Avatar for Anton Arhipov

Anton Arhipov

October 06, 2021
Tweet

More Decks by Anton Arhipov

Other Decks in Programming

Transcript

  1. Idiomatic - using, containing, or denoting expressions that are natural

    to a native speaker Commonly accepted style Effective use of features
  2. fun main() { doSomething() } fun doSomething() { doMoreStuff( :

    : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") }
  3. fun main() { doSomething() } fun doSomething() { doMoreStuff( :

    : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") }
  4. fun main() { doSomething() } fun doSomething() { doMoreStuff( :

    : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") }
  5. fun main() { doSomething() } fun doSomething() { doMoreStuff( :

    : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") } Just functions, no classes!
  6. fun main() { doSomething() } fun doSomething() { doMoreStuff( :

    : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") } Grouping by fi les …
  7. fun main() { doSomething() } fun doSomething() { doMoreStuff( :

    : finishWork) } fun doMoreStuff(callback: () -> Unit) { callback() } fun finishWork() { TODO("Not implemented yet") } Grouping by fi les …
  8. Don’t create classes just to hold functions class StringUtils {

    companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } }
  9. Don’t create classes just to hold functions class StringUtils {

    companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } }
  10. Don’t create classes just to hold functions class StringUtils {

    companion object { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() }
  11. Use extension functions class StringUtils { companion object { fun

    isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } } object StringUtils { fun isPhoneNumber(s: String) = s.length = = 7 & & s.all { it.isDigit() } } fun isPhoneNumber(s: String) = s.length == 7 && s.all { it.isDigit() } fun String.isPhoneNumber() = length == 7 && all { it.isDigit() }
  12. Extension or a member? https://kotlinlang.org/docs/coding-conventions.html#extension-functions Use extension functions liberally. Minimize

    API pollution, restrict the visibility. As necessary, use local extension functions, member extension functions, or top-level extension functions with private visibility.
  13. +

  14. fun findMessageById(id: String) = db.query( "select * from messages where

    id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id )
  15. fun findMessageById(id: String) = db.query( "select * from messages where

    id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id ) @Override public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object . .. args) throws DataAccessException { return result(query(sql, args, new RowMapperResultSetExtractor < > (rowMapper))); }
  16. fun findMessageById(id: String) = db.query( "select * from messages where

    id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id )
  17. fun findMessageById(id: String) = db.query( "select * from messages where

    id = ?", id, RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) } )
  18. fun findMessageById(id: String) = db.query( "select * from messages where

    id = ?", id, { rs, _ -> Message(rs.getString("id"), rs.getString("text")) } )
  19. fun findMessageById(id: String) = db.query( "select * from messages where

    id = ?", id) { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }
  20. fun findMessageById(id: String) = db.query( "select * from messages where

    id = ?", id) { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }
  21. fun findMessageById(id: String) = db.query( "select * from messages where

    id = ?", id) { rs, _ -> Message(rs.getString("id"), rs.getString("text")) } fun <T> JdbcOperations.query(sql: String, vararg args: Any, function: (ResultSet, Int) -> T): List<T> = query(sql, RowMapper { rs, i -> function(rs, i) }, *args)
  22. val dataSource = BasicDataSource( ) dataSource.driverClassName = "com.mysql.jdbc.Driver" dataSource.url =

    "jdbc:mysql://domain:3309/db" dataSource.username = "username" dataSource.password = "password" dataSource.maxTotal = 40 dataSource.maxIdle = 40 dataSource.minIdle = 4
  23. val dataSource = BasicDataSource( ) dataSource.driverClassName = "com.mysql.jdbc.Driver" dataSource.url =

    "jdbc:mysql://domain:3309/db" dataSource.username = "username" dataSource.password = "password" dataSource.maxTotal = 40 dataSource.maxIdle = 40 dataSource.minIdle = 4 val dataSource = BasicDataSource().apply { driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://domain:3309/db" username = "username" password = "password" maxTotal = 40 maxIdle = 40 minIdle = 4 }
  24. val dataSource = BasicDataSource().apply { driverClassName = "com.mysql.jdbc.Driver" url =

    "jdbc:mysql://domain:3309/db" username = "username" password = "password" maxTotal = 40 maxIdle = 40 minIdle = 4 } public inline fun <T> T.apply(block: T.() -> Unit): T { block() return this }
  25. val dataSource = BasicDataSource().apply { driverClassName = "com.mysql.jdbc.Driver" url =

    "jdbc:mysql://domain:3309/db" username = "username" password = "password" maxTotal = 40 maxIdle = 40 minIdle = 4 } public inline fun <T> T.apply(block: T.() -> Unit): T { block() return this } Lambda with receiver
  26. val order = retrieveOrder() if (order != null){ processCustomer(order.customer) }

    retrieveOrder() ?. let { processCustomer(it.customer) } retrieveOrder() ?. customer ?. let { :: processCustomer } or ?.let
  27. val order = retrieveOrder() if (order != null){ processCustomer(order.customer) }

    retrieveOrder() ?. let { processCustomer(it.customer) } No extra variable retrieveOrder() ?. let { processCustomer(it.customer) } retrieveOrder() ?. customer ?. let { :: processCustomer } or ?.let
  28. fun find(name: String){ find(name, true) } fun find(name: String, recursive:

    Boolean){ } fun find(name: String, recursive: Boolean = true){ } Default argument value Function overloading
  29. fun find(name: String){ find(name, true) } fun find(name: String, recursive:

    Boolean){ } fun find(name: String, recursive: Boolean = true){ } fun main() { find("myfile.txt") } Default argument value Function overloading
  30. 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", ) Figure(Color.RED, "Red figure")
  31. 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", ) Figure(Color.RED, "Red figure") Compilation error
  32. 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", ) Figure(color = Color.RED, description = "Red figure")
  33. Default argument values diminish the need for overloading in most

    cases. Named parameters is a necessary tool for working with default argument values
  34. fun adjustSpeed(weather: Weather): Drive { val result: Drive if (weather

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

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

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

    (weather is Rainy) { Safe() } else { Calm() } return result }
  38. fun adjustSpeed(weather: Weather): Drive = ... 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
  39. abstract class Weather class Sunny : Weather() class Rainy :

    Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() else -> Calm() }
  40. sealed class Weather class Sunny : Weather() class Rainy :

    Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() / / else -> Calm() }
  41. sealed class Weather class Sunny : Weather() class Rainy :

    Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() / / else -> Calm() }
  42. sealed class Weather class Sunny : Weather() class Rainy :

    Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() is Sunny -> TODO() }
  43. sealed class Weather class Sunny : Weather() class Rainy :

    Weather() fun adjustSpeed(weather: Weather) = when (weather) { is Rainy -> Safe() is Sunny -> TODO() } Use expressions! Use when as expression body Use sealed classes with when
  44. Use try as expression body fun tryParse(number: String) : Int?

    { try { return Integer.parseInt(number) } catch (e: NumberFormatException) { return null } }
  45. Use try as expression body fun tryParse(number: String) = try

    { Integer.parseInt(number) } catch (e: NumberFormatException) { null }
  46. Use try as expression fun tryParse(number: String) : Int? {

    val n = try { Integer.parseInt(number) } catch (e: NumberFormatException) { null } println(n) return n }
  47. class Nullable { fun someFunction(){} } fun createNullable(): Nullable? =

    null fun main() { val n: Nullable? = createNullable() n.someFunction() }
  48. class Nullable { fun someFunction(){} } fun createNullable(): Nullable? =

    null fun main() { val n: Nullable? = createNullable() n.someFunction() }
  49. Consider 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
  50. Consider using null-safe call val order = retrieveOrder() val city

    = order ?. customer ? . address ?. city ?: throw IllegalArgumentException("Invalid Order")
  51. Avoid not-null assertions !! 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
  52. Avoid not-null assertions !! 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) } }
  53. Avoid not-null assertions !! 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) } } - use lateinit
  54. 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") }
  55. 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") }
  56. 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") }
  57. 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") }
  58. Consider using safe cast for type checking override fun equals(other:

    Any?) : Boolean { val command = other as Command return command.id == id }
  59. 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 }
  60. fun <T> someFunction(function: () -> T): T { … }

    fun someOtherFunction(){ val s: String = someFunction { "Hello" } }
  61. fun <T> someFunction(function: Action<T>): T { … } fun someOtherFunction(){

    val s: String = someFunction { "Hello" } } typealias Action<T> = () -> T
  62. typealias Action<T> = () -> T class MyAction<T> : Action<T>

    { override fun invoke(): T { TODO("Not yet implemented") } }
  63. typealias Action<T> = () -> T class MyAction<T> : Action<T>

    { override fun invoke(): T { TODO("Not yet implemented") } } fun <T> someFunction(function: Action<T>): T { … }
  64. typealias Action<T> = () -> T class MyAction<T>(val param: String)

    : Action<T> { override fun invoke(): T { TODO("Not yet implemented") } } fun <T> someFunction(function: Action<T>): T { … } fun someOtherFunction(){ val s: String = someFunction(MyAction("Greetings")) }
  65. It’s not enough for the programming language to provide higher-order

    functions. We need other tools for working with these! Observation:
  66. It’s not enough for the programming language to provide FEATURE.

    We need other tools for working with FEATURE! General observation:
  67. 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
  68. 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)
  69. 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)
  70. Ranges in loops fun main(args: Array<String>) { for (i in

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

    0 .. args.size - 1) { println("$i: ${args[i]}") } }
  72. 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]}") }
  73. 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]}") }
  74. 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, arg) in args.withIndex()) { println("$i: $arg") }
  75. Return multiple values using data classes fun namedNum(): Pair<Int, String>

    = 1 to "one" // same but shorter fun namedNum2() = 1 to "one" fun main(args: Array<String>) { val pair = namedNum() val number = pair.first val name = pair.second }
  76. Return multiple values using data classes fun namedNum(): Pair<Int, String>

    = 1 to "one" // same but shorter fun namedNum2() = 1 to "one" fun main(args: Array<String>) { val pair = namedNum() val number = pair.first val name = pair.second } data class GameResult( val rank: Int, val name: String ) fun namedNum() = GameResult(1, "Player 1") fun main(args: Array<String>) { val (rank, name) = namedNum() println("$name, rank $rank") }
  77. Return multiple values using data classes data class GameResult( val

    rank: Int, val name: String ) fun namedNum() = GameResult(1, "Player 1") fun main(args: Array<String>) { val (rank, name) = namedNum() println("$name, rank $rank") } GameResult var1 = namedNum(); int var2 = var1.component1(); String var3 = var1.component2();
  78. Destructuring in loops fun printMap(map: Map<String, String>) { for (item

    in map.entries) { println("${item.key} -> ${item.value}") } }
  79. Destructuring in loops fun printMap(map: Map<String, String>) { for (item

    in map.entries) { println("${item.key} -> ${item.value}") } } fun printMap(map: Map<String, String>) { for ((key, value) in map) { println("$key -> $value") } }
  80. Destructuring in lists data class NameExt( val name: String, val

    ext: String? ) fun splitNameExt(filename: String): NameExt { if ('.' in filename) { val parts = filename.split('.', limit = 2) return NameExt(parts[0], parts[1]) } return NameExt(filename, null) } fun splitNameAndExtension(filename: String): NameExt { if ('.' in filename) { val (name, ext) = filename.split('.', limit = 2) return NameExt(name, ext) } return NameExt(filename, null) }
  81. abcd bcde cdef defg xya xyb xyc Group 1: Group

    2: Count the total of characters that are present on each line in every group of strings
  82. abcd bcde cdef defg xya xyb xyc Group 1: Group

    2: Count the total of characters that are present on each line in every group of strings
  83. abcd bcde cdef defg xya xyb xyc Group 1: Group

    2: Count the total of characters that are present on each line in every group of strings {d} {xy}
  84. abcd bcde cdef defg xya xyb xyc Group 1: Group

    2: Count the total of characters that are present on each line in every group of strings {d} {xy} count = 1 count = 2
  85. abcd bcde cdef defg xya xyb xyc Group 1: Group

    2: Count the total of characters that are present on each line in every group of strings {d} {xy} count = 1 count = 2 Total = 3
  86. val input = """ abcd bcde cdef defg xya xyb

    xyc """.trimIndent() val groups: List<String> = input.split("\n\n")
  87. val input = """ abcd bcde cdef defg xya xyb

    xyc """.trimIndent() val groups: List<String> = input.split("\n\n")
  88. val input = """ abcd bcde cdef defg xya xyb

    xyc """.trimIndent() val groups: List<String> = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List<Set<Char >> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() }
  89. val input = """ abcd bcde cdef defg xya xyb

    xyc """.trimIndent() val groups: List<String> = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List<Set<Char >> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() }
  90. val input = """ abcd bcde cdef defg xya xyb

    xyc """.trimIndent() val groups: List<String> = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List<Set<Char >> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() }
  91. val input = """ abcd bcde cdef defg xya xyb

    xyc """.trimIndent() val groups: List<String> = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List<Set<Char >> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() }
  92. val input = """ abcd bcde cdef defg xya xyb

    xyc """.trimIndent() val groups: List<String> = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List<Set<Char >> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() } Transforming data
  93. val input = """ abcd bcde cdef defg xya xyb

    xyc """.trimIndent() val groups: List<String> = input.split("\n\n") var total = 0 for (group in groups) { val listOfSets: List<Set<Char >> = group.split("\n").map(String :: toSet) var result = listOfSets.first() for (set in listOfSets) { result = result intersect set } total += result.count() } Transforming data Calculating the result
  94. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } Transforming data
  95. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } Transforming data
  96. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } Transforming data
  97. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } Transforming data
  98. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } Transforming data
  99. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result
  100. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result
  101. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result
  102. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result
  103. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result
  104. val groups: List<String> = input.split("\n\n") // List<List<Set<Char >>> val step1

    = groups.map { it.split("\n").map(String :: toSet) } val step2 = step1.sumOf { it.reduce { a, b - > a intersect b }.count() } Transforming data Calculating the result groups.map { group -> group.split(nl).map(String :: toSet) }.sumOf { answerSets -> answerSets.reduce { a, b -> a intersect b }.count() }
  105. Select objects by type with filterIsInstance fun findAllStrings(objects: List<Any>) =

    objects.filter { it is String } fun findAllStrings(objects: List<Any>) = objects.filterIsInstance<String>()
  106. Select objects by type with filterIsInstance fun findAllStrings(objects: List<Any>) :

    List<Any> = objects.filter { it is String } fun findAllStrings(objects: List<Any>) : List<String> = objects.filterIsInstance<String>()
  107. compareBy compares by multiple keys class Person( val name: String,

    val age: Int ) fun sortPersons(persons: List<Person>) = persons.sortedWith(Comparator<Person> { person1, person2 -> val rc = person1.name.compareTo(person2.name) if (rc != 0) rc else person1.age - person2.age })
  108. compareBy compares by multiple keys class Person( val name: String,

    val age: Int ) fun sortPersons(persons: List<Person>) = persons.sortedWith(Comparator<Person> { person1, person2 -> val rc = person1.name.compareTo(person2.name) if (rc != 0) rc else person1.age - person2.age }) fun sortPersons(persons: List<Person>) = persons.sortedWith(compareBy(Person :: name, Person : : age))
  109. groupBy to group elements class Request( val url: String, val

    remoteIP: String, val timestamp: Long ) fun analyzeLog(log: List<Request>) { val map = mutableMapOf<String, MutableList<Request >> () for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } }
  110. groupBy to group elements class Request( val url: String, val

    remoteIP: String, val timestamp: Long ) fun analyzeLog(log: List<Request>) { val map = mutableMapOf<String, MutableList<Request >> () for (request in log) { map.getOrPut(request.url) { mutableListOf() } .add(request) } } fun analyzeLog(log: List<Request>) { val map = log.groupBy(Request :: url) }
  111. Use coerceIn to ensure numbers in range fun updateProgress(value: Int)

    { val actualValue = when { value < 0 - > 0 value > 100 -> 100 else - > value } } fun updateProgress(value: Int) { val actualValue = value.coerceIn(0, 100) }
  112. Initializing objects with apply val dataSource = BasicDataSource( ) dataSource.driverClassName

    = "com.mysql.jdbc.Driver" dataSource.url = "jdbc:mysql://domain:3309/db" dataSource.username = "username" dataSource.password = "password" dataSource.maxTotal = 40 dataSource.maxIdle = 40 dataSource.minIdle = 4 val dataSource = BasicDataSource().apply { driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://domain:3309/db" username = "username" password = "password" maxTotal = 40 maxIdle = 40 minIdle = 4 }
  113. Initializing objects with apply final ClientBuilder builder = new ClientBuilder();

    builder.setFirstName("Anton"); builder.setLastName("Arhipov"); final TwitterBuilder twitterBuilder = new TwitterBuilder(); twitterBuilder.setHandle("@antonarhipov"); builder.setTwitter(twitterBuilder.build()); final CompanyBuilder companyBuilder = new CompanyBuilder(); companyBuilder.setName("JetBrains"); companyBuilder.setCity("Tallinn"); builder.setCompany(companyBuilder.build()); final Client client = builder.build(); System.out.println("Created client is: " + client);
  114. Initializing objects with apply val builder = ClientBuilder() builder.firstName =

    "Anton" builder.lastName = "Arhipov" val twitterBuilder = TwitterBuilder() twitterBuilder.handle = "@antonarhipov" builder.twitter = twitterBuilder.build() val companyBuilder = CompanyBuilder() companyBuilder.name = "JetBrains" companyBuilder.city = "Tallinn" builder.company = companyBuilder.build() val client = builder.build() println("Created client is: $client")
  115. Initializing objects with apply val builder = ClientBuilder() builder.firstName =

    "Anton" builder.lastName = "Arhipov" val twitterBuilder = TwitterBuilder() twitterBuilder.handle = "@antonarhipov" builder.twitter = twitterBuilder.build() val companyBuilder = CompanyBuilder() companyBuilder.name = "JetBrains" companyBuilder.city = "Tallinn" builder.company = companyBuilder.build() val client = builder.build() println("Created client is: $client") val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  116. buildString //Java 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); //Kotlin val name = "Joe" val s = buildString { repeat(5) { append("Hello, ") append(name) appendLine("!") } } println(s)
  117. Ktor 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) }
  118. Ktor 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
  119. Ktor 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) } kotlinx.html Ktor’s routing
  120. Build your vocabulary to abstract from scope functions val client

    = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  121. Build your vocabulary to abstract from scope functions fun client(c:

    ClientBuilder.() - > Unit): Client { val builder = ClientBuilder() c(builder) return builder.build() } fun ClientBuilder.company(block: CompanyBuilder.() - > Unit) { company = CompanyBuilder().apply(block).build() } fun ClientBuilder.twitter(block: TwitterBuilder.() - > Unit) { twitter = TwitterBuilder().apply(block).build() } val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")
  122. val client = client { firstName = "Anton" lastName =

    "Arhipov" twitter { handle = "@antonarhipov" } company { name = "JetBrains" city = "Tallinn" } } println("Created client is: $client") Build your vocabulary to abstract from scope functions val client = ClientBuilder().apply { firstName = "Anton" lastName = "Arhipov" twitter = TwitterBuilder().apply { handle = "@antonarhipov" }.build() company = CompanyBuilder().apply { name = "JetBrains" city = "Tallinn" }.build() }.build() println("Created client is: $client")