7. Idiomatic - using, containing, or denoting expressions
that are natural to a native speaker
8. Idiomatic - using, containing, or denoting expressions
that are natural to a native speaker
In case of a programming language:
•Conforms to a commonly accepted style
•E
ff
ectively uses features of the programming language
26. fun adjustSpeed(weather: Weather) = if (weather is Rainy) Safe() else Calm()
fun adjustSpeed(weather: Weather): Drive {
var result: Drive
if (weather is Rainy) {
result = Safe()
} else {
result = Calm()
}
return result
}
29. abstract class Weather
class Sunny : Weather()
class Rainy : Weather()
fun adjustSpeed(weather: Weather) = when (weather) {
is Rainy
-
>
Safe()
else
-
>
Calm()
}
30. sealed class Weather
class Sunny : Weather()
class Rainy : Weather()
fun adjustSpeed(weather: Weather) = when (weather) {
is Rainy
-
>
Safe()
/
/
else
-
>
Calm()
}
31. sealed class Weather
class Sunny : Weather()
class Rainy : Weather()
fun adjustSpeed(weather: Weather) = when (weather) {
is Rainy
-
>
Safe()
/
/
else
-
>
Calm()
}
32. sealed class Weather
class Sunny : Weather()
class Rainy : Weather()
fun adjustSpeed(weather: Weather) = when (weather) {
is Rainy
-
>
Safe()
is Sunny
-
>
TODO()
}
33. 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
34. Use try as expression body
fun tryParse(number: String) : Int? {
try {
return Integer.parseInt(number)
} catch (e: NumberFormatException) {
return null
}
}
35. Use try as expression body
fun tryParse(number: String) = try {
Integer.parseInt(number)
} catch (e: NumberFormatException) {
null
}
36. Use try as expression
fun tryParse(number: String) : Int? {
val n = try {
Integer.parseInt(number)
} catch (e: NumberFormatException) {
null
}
println(n)
return n
}
37. Use elvis operator
class Person(val name: String?, val age: Int?)
val p = retrievePerson()
?
:
Person()
38. 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")
}
39. 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")
}
40. 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")
}
41. 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")
}
43. 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
44. Consider using null-safe call
val order = retrieveOrder()
val city = order
?
.
customer
?
.
address
?
.
city
45. Consider using null-safe call
val order = retrieveOrder()
val city = order
?
.
customer
?
.
address
?
.
city
?
:
throw IllegalArgumentException("Invalid Order")
46. 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
47. 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)
}
}
48. 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
49. Consider using ?.let for null-checks
val order = retrieveOrder()
if (order
!
=
null){
processCustomer(order.customer)
}
50. Consider using ?.let for null-checks
val order = retrieveOrder()
if (order
!
=
null){
processCustomer(order.customer)
}
retrieveOrder()
?
.
let {
processCustomer(it.customer)
}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
51. Consider using ?.let for null-checks
val order = retrieveOrder()
if (order
!
=
null){
processCustomer(order.customer)
}
retrieveOrder()
?
.
let {
processCustomer(it.customer)
}
No need for an extra variable
retrieveOrder()
?
.
let {
processCustomer(it.customer)
}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
52. Consider using ?.let for null-checks
val order = retrieveOrder()
if (order
!
=
null){
processCustomer(order.customer)
}
retrieveOrder()
?
.
let {
processCustomer(it.customer)
}
retrieveOrder()
?
.
let {
processCustomer(it.customer)
}
retrieveOrder()
?
.
customer
?
.
let {
:
:
processCustomer }
or
53. Consider using safe cast for type checking
override fun equals(other: Any?) : Boolean {
val command = other as Command
return command.id
=
=
id
}
54. 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
}
55. Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =
c
>
=
'A'
&
&
c
<
=
'Z'
56. Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =
c
>
=
'A'
&
&
c
<
=
'Z'
57. Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =
c in 'A'
.
.
'Z'
58. Use range checks instead of comparison pairs
fun isLatinUppercase(c: Char) =
c in 'A'
.
.
'Z'
59. Ranges in loops
fun main(args: Array<String>) {
for (i in 0
.
.
args.size - 1) {
println("$i: ${args[i]}")
}
}
60. Ranges in loops
fun main(args: Array<String>) {
for (i in 0
.
.
args.size - 1) {
println("$i: ${args[i]}")
}
}
61. 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]}")
}
62. 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]}")
}
63. 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")
}
69. Extension or a member?
https://kotlinlang.org/docs/coding-conventions.html#extension-functions
•Use extension functions liberally.
•If a function works primarily on an object, consider making it an
extension with that object as a receiver.
•Minimize API pollution, restrict the visibility.
•As necessary, use local extension functions, member extension
functions, or top-level extension functions with private visibility.
70. Use default values instead of overloading
class Phonebook {
fun print() {
print(",")
}
fun print(columnSeparator: String) {}
}
fun main(args: Array<String>) {
Phonebook().print("|")
}
71. Use default values instead of overloading
class Phonebook {
fun print() {
print(",")
}
fun print(columnSeparator: String) {}
}
fun main(args: Array<String>) {
Phonebook().print("|")
}
class Phonebook {
fun print(separator: String = ",") {}
fun someFun(x: Int) {}
}
fun main(args: Array<String>) {
Phonebook().print(separator = "|")
}
72. 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
}
73. 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")
}
74. 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();
75. Destructuring in loops
fun printMap(map: Map<String, String>) {
for (item in map.entries) {
println("${item.key}
-
>
${item.value}")
}
}
76. 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")
}
}
77. 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)
}
78.
79. Use type aliases for functional types
class Event
class EventDispatcher {
fun addClickHandler(handler: (Event)
-
>
Unit) {}
fun removeClickHandler(handler: (Event)
-
>
Unit) {}
}
80. Use type aliases for functional types
class Event
class EventDispatcher {
fun addClickHandler(handler: (Event)
-
>
Unit) {}
fun removeClickHandler(handler: (Event)
-
>
Unit) {}
}
typealias ClickHandler = (Event)
-
>
Unit
class EventDispatcher {
fun addClickHandler(handler: ClickHandler) {
}
fun removeClickHandler(handler: ClickHandler) {
}
}
82. Verify parameters using require()
class Person(
val name: String?,
val age: Int
)
fun processPerson(person: Person) {
if (person.age < 18) {
throw IllegalArgumentException("Adult required")
}
}
83. Verify parameters using require()
class Person(
val name: String?,
val age: Int
)
fun processPerson(person: Person) {
if (person.age < 18) {
throw IllegalArgumentException("Adult required")
}
}
fun processPerson(person: Person) {
require(person.age
>
=
18) { "Adult required" }
}
84. Select objects by type with filterIsInstance
fun findAllStrings(objects: List<Any>) =
objects.filter { it is String }
85. Select objects by type with filterIsInstance
fun findAllStrings(objects: List<Any>) =
objects.filter { it is String }
fun findAllStrings(objects: List<Any>) =
objects.filterIsInstance<String>()
86. 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>()
87. Apply operation to non-null elements mapNotNull
data class Result(
val data: Any?,
val error: String?
)
fun listErrors(results: List<Result>): List<String> =
results.map { it.error }.filterNotNull()
fun listErrors(results: List<Result>): List<String> =
results.mapNotNull { it.errorMessage }
88. 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
})
89. 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))
90. 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)
}
}
91. 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)
}
92. 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)
}
94. 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);
95. 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")
96. 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")
99. “Domain Speci
fi
c”,
i.e. tailored for a speci
fi
c task
Examples:
•Compose strings - stringBuilder
•Create HTML documents - kotlinx.html
•Con
fi
gure routing logic for a web app - ktor
•Generally, build any object graphs. See “type-safe builders”
100. 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)