Artem Makarov
Senior Software Developer @ Code Nomads
10 years of software development experience
3 years of Kotlin experience
Worked at Financial, Retail and Health industries
Artem Makarov @artemy_m
Artem Makarov
Senior Software Developer @ Code Nomads
10 years of software development experience
3 years of Kotlin experience
Worked at Financial, Retail and Health industries
Performance
Coroutines
Kotlin on Android
Kotlin/Native
Development started in 2010 at JetBrains
Motivation:
Java’s lack of modern features
Scala poor tooling
2012: code became open source
2016: v1.0 release
2017: Google announces first-class support on Android
2019: Google makes Kotlin preferred language for Android app development
checked exceptions
primitive types
ternary operator (x ? a : b
)
public fields
semicolon line termination
new
keyword
data classes*
string templates
operator overloading
trailing lambdas
named & default arguments
lazy initialization
coroutines
Mutability is controlled with val
/var
keywords
var mutableBuffer = "Example Buffer" // mutable variable
val immutableBuffer = "Example Buffer" // immutable variable
val explicitType: String = "Foo Bar" // explicit type declaration
fun sum(a: Int, b: Int): Int {
return a + b
}
// function with expression body
fun multiply(a: Int, b: Int) = a * b
// function that doesn't any meaningful values
fun logWarn(message: String): Unit { /*...*/ }
// Unit is optional
fun logInfo(message: String) { /*...*/ }
Kotlin mandates explicitly defining nullable types with ?
symbol at the end
fun parseDate(input: String): LocalDate? { /*...*/ }
val date: LocalDate? = parseDate("2022-01-02")
// using returned nullable value
print(date?.month) // safe call with ?.
print(date!!.month) // unsafe call
// using if-else
val month = if (date.month != null) date.month else LocalDate.now().month
// simplified notation with elvis operator
val month2 = date.month ?: LocalDate.now().month
Kotlin compiler keeps track of nullability checks performed
val date2: LocalDate? = parseDate("2022-01-02")
if (date2 != null) {
print(date2) // smart cast to non-nullable
} else {
print("Unable to parse date")
}
Classes in Kotlin can have a primary constructor and several secondary constructors
// class with implicit constructor
class Author(name: String) {/* ... */ }
// explicit constructor with visibility modifiers
class Publisher protected constructor(val books: MutableList<Book> = mutableListOf())
class Book(name: String) {
constructor(name: String, publisher: Publisher) : this(name) {
publisher.books.add(this)
}
}
If the class also has a primary constructor, each secondary constructor needs to delegate to the primary constructor |
// yay no more Lombok!
data class Person(
val firstName: String,
val lastName: String,
val active: Boolean = true
)
val person = Person("Jon", "Snow")
val person2 = Person(firstName = "Jon", lastName = "Snow", active = true)
data class Container(val element: String)
val container1 = Container(element = "foo")
// copy of container 1
val container2 = container1.copy()
// altered copy of container 1
val container3 = container1.copy(element = "bar")
When there’s a need to return several values from a function, a data class can be used with a destructuring declaration
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(...)
Objects can also be declared as singletons
object MyAwesomeObject {
fun myAwesomeMethod() {}
val container: Collection<String>
get() = /* ... */
fun staticMethod() {} // static method
}
// referring to the object methods
val temp = MyAwesomeObject.myAwesomeMethod(/* ... */)
Objects can be declared inside a class using with the companion
keyword
class User {
companion object {
private const val USER_TABLE = "users"
fun create(): User { /* ... */ }
}
}
companion members are members of object instance, i.e. not really static. To generate them as static, use @JvmStatic annotation |
It is possible to write new functions for a class or an interface without modifying the class itself
fun Car.ofMake(make: Make) =
this.make == make // receiver object is available by `this`
It is possible to define generic extension functions
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
Kotlin also supports extension properties the same way as functions
val Book.firstAuthor: Author
get() = authors.first()
It is possible to overload predefined operators on types (e.g. +, *, -, etc.)
data class Shape(val shape: Shape) {
operator fun plus(s2: Shape): Shape { /*...*/ }
}
Functions marked with infix
can be called using the infix notation (without the dot and the parentheses for the call)
infix fun Car.ofMake(make: Make): Boolean = make == this.make
// usage
if (car ofMake Make.VOLKSWAGEN)
println("This is a Volkswagen")
// same as
if (car.ofMake(Make.VOLKSWAGEN))
println("This is a Volkswagen")
Type aliases are used to provide alternative names for existing types, e.g. when the name is too long
typealias IntList = List<Int>
// shortening generic types
typealias StringTable<K> = MutableMap<K, MutableList<String>>
// aliases for function types
typealias Predicate<T> = (T) -> Boolean
Type alises do not introduce new types, so you can pass a variable of your aliased type whenever general function type is required (and vice versa)
typealias Predicate2<T> = (T) -> Boolean
fun foo(p: Predicate2<Int>) = p(42)
fun main() {
val f: (Int) -> Boolean = { it > 0 }
println(foo(f)) // prints "true"
val p: Predicate2<Int> = { it > 0 }
println(listOf(1, -2).filter(p)) // prints "[1]"
}
Kotlin standard library offers several functions for executing code within the context of an object.
Within this scope of the function the object can be accessed without using its name (i.e. via it
or this
)
val str: String? = "Hello"
val uppercase = str?.let { it.toUpperCase() }
// without let
val str2: String? = "Hello"
val uppercase2 = if (str2 != null) str2.toUpperCase() else null
fun findById(id: Long): Example {
return exampleRepository.findById(id)
.also { logger.info("retrieved record with $id") }
}
In Kotlin, functions are first-class, so they can be stored in variables and data structures and passed as arguments and returned from other high-order functions
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
// usage
val concatenate = listOf("FOO", "BAR")
.fold("") { acc: String, input: String -> acc + input }
println(concatenate) // prints "FOOBAR"
Kotlin offers more concise API for dealing with stream-like operations
Java:
List<String> filteredStrings = List.of("cola", "coffee", "champagne").stream()
.filter(str -> str.startsWith("c"))
.collect(Collectors.toList());
Kotlin:
val filteredStrings = listOf("cola", "coffee", "champagne")
.filter { it.startsWith("c") } // automatically collected to List<T>
Kotlin collection operations are not lazy, like in Java
Integer firstOddNumber = List.of(1, 2, 3, 4, 5).stream()
.filter(n -> n % 2 == 0)
.findFirst() // will only cycle through the first one
.get();
val filteredStrings2 = listOf("cola", "coffee", "champagne")
.filter { it.startsWith("c") }
.first() // will cycle through all items
Lazy evaluation of sequences can be enforced via using asSequence()
method
val filteredStrings3 = listOf("cola", "coffee", "champagne").asSequence()
.filter { it.startsWith("c") }
.first() // will only take the first item
// inside the curly brackets there is a valid Kotlin expression
val message = "Added ${records.count()} records"
// no need for curly brackets for simple objects
val anotherMessage = "Added $recordCount records"
Try to think about Kotlin as a separate programming language rather than a Java library *
Take note of the IntelliJ Warnings and hints :-)
Kotlin Docs: https://kotlinlang.org/docs/home.html
Kotlin Idioms: https://kotlinlang.org/docs/idioms.html
Kotlin Coding: https://kotlinlang.org/docs/coding-conventions.html
Kotlin Koans: https://play.kotlinlang.org/koans/overview
Introduction to Coroutines by Roman Elizarov: https://www.youtube.com/watch?v=_hfBv0a09Jc
Repository with the slides: https://github.com/artemy/kotlin-for-java-talk