Skip to main content

Kotlin 🐈

Kotlin is compiled to Java bytecode, which provides the backward compatability. When wishing to see Kotlin in real-life use J2K converter can be used to convert part of source code into Kotlin. Another way is to start writing unit tests in Kotlin.

Kotlin standard library is just Java standard library and a bunch of extensions that provides very smooth interoperability between Java code and Kotlin code.

In IntelliJ we can use the Show Kotlin bytecode + Decompile features to see the Java alternative for the Kotlin code.

Reference on Kotlin conventions

Kotlin roadmap

class / object

Modifiers

We can declare constants with const modifier, and it's going to be substituted with the value on the JVM level. It works only for primitive types.

modifierexplanation
finalcannot be overridden (is sed by default)
opencan be overridden
abstractmust be overridden (can't have implementation)
overrideoverrides a member in a superclass or interface (mandatory)
modifierclass membertop-level declaration
publicvisible everywherevisible everywhere
internalvisible in the modulevisible in the module
protectedvisible in subclasses (only)
privatevisible in the classvisible in the file
kotlin modifierjvm level
publicpublic
protectedprotected
privateprivate / package private
internalpublic & name ruining

enum class

In kotlin enum is a modifier for classes to create enumerations

data class

Generates equals, hashCode, copy, toString

equals and reference equality

In kotlin == calls equals. There is also === operator which checks reference equality. Bare class eqauls uses reference equality check.

val set1 = setOf(1, 2, 3)
val set2 = setOf(1, 2, 3)

set1 == set2 // true
set1 === set2 // false

We can still exclude props from the basic methods in a data classby moving them outside the primary constructor.

data class User(val email: String) {
val nickname: String? = null
}

sealed class

Restricts class hierarchy, all subclasses must be located in the sames file.

Class delegation with by

We can delegate methods from one class another. We don't need to write boilerplate code for it.

interface Printer {
fun printMessage(message: String)
}

class ConsolePrinter : Printer {
override fun printMessage(message: String) {
pirntln(message)
}
}

class PrinterManager(printer: Printer) : Printer by printer

fun main() {
val consolePrinter = ConsolePrinter()
val manager = PrinterManager(consolePrinter)

manager.printMessage("Hi there!") // <-- we are calling a method, the code of which
// we did not write in PrinterManager explicitly
}

Package structure

In kotlin we can put multiple classes inside one file, this file can also contain top-level statements

Inheritance

interface Base
class BaseImpl : Base
open class Parent
class Child : Parent() // <-- () is the constructor call, so we can pass parameters there if any

object

object = singleton

java

public class JSingleton {
public final static JSingleton INSTANCE = new JSingleton();

private JSignleton() {
}

private foo() {
}
}

kotlin

object KSingleton {
fun foo() {}
}

companion object

In kotlin there are no static methods and companion objects might be a replacement for that.

class A {
companion object {
fun foo() = 1
}
}

fun main(args: Array<String>) {
A.foo()
}

Companion objects can implement interfaces and be receiver of extension function.

interface Factory<T> {
fun create(): T
}

class A {
private constructor()

companion object : Factory<A> {
override fun create(): A {
return A()
}
}
}

fun <T> createNewInstance(factory: Factory<T>) { /* some code */
}

createNewInstance(A)
A.create()
class Person(val firstName: String, val lastName: String) {
companion object {}
}

fun Person.Companion.fromJson(json: String): Person {
// ...
}

val p = Person.fromJson(json)

Not all objects are singletons, object expressions are the java's anonymous class alternative. They are used for the cases when we have to override multiple methods, otherwise we could just use lambdas.

widnow.addMouseListener() {
object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ..
}

override fun mouseEntered(e: MouseEvent) {
// ..
}
}
}

Functions

There are no static members in kotlin. The closest thing to that would be:

  • top-level statements
  • objects' members
  • companion objects' members

Calling a top-level function from Java:

package intro

fun foo() = 0
packge other;

import intro.MyFileKt;

public class UsingFoo {
public static void main(String[] args) {
MyFileKt.foo();
}
}

We can use the @JvmName to change the name of the package to import.

@file:JvmName("Util")

package intro

fun foo() = 0
packge other;

import static intro.Util;

public class JavaUsage {
public static void main(String[] args) {
into i = Util.foo();
}
}

Constructors

class A

val a = A() // <-- calling a default constructor

Full primary constructor syntax looks like this. val or var in the constructor would automatically create a property. Constructor's visibility can be changed.

class Person(name: String) { // <-- name is a constructor parameter
val name: String

init {
this.name = name
}
}

We can declare secondary constructors and must use the primary constructor.

class Rectangle(val height: Int, val width: Int) {
constructor(side: Int) : this(side, side) { /* some logic */
}
}

Properties

Kotlin

contact.address
contact.address = "..."

Java

contact.getAddress();
contact.

setAdderss("...");

property = accessor(s)

val = getter

var = getter + setter

If the field is not mentioned in custom accessor then no backing field is generated. All properties are open.

Lazy and late initialization

Lazy props' values is calculated on the first access.

val lazyValue: String by lazy {
println("completed!")
"Hello"
}

lateinit is useful when we want to initialize the values not in the constructor and not to use nullable accessors everywhere. If the property was not initialize a runtime UninitializedPropertyAccessException is thrown. lateinit can't be val, can't be nullable or of a primitive type.

class KotlinActivity : Activity() {
lateinit var myData: MyData

override fun onCreate(savedInstanceState: Budnle?) {
super.onCreate(savedInstanceState)

myData = intent.getParcelableExtra("MY_DATA")
}
}
myData.foo // we can call props of myData with no safe accessors

Exceptions

In Kotlin, there is no difference between checked and unchecked exceptions. There are no checked exceptions in Kotlin so there is no need to specify this function throws at this exception. Kotlin's library still has a @Throws annotation. When we throw a checked exception from Java point of view in Kotlin and want to later handle it in Java, we need to add this annotation.

Java code calling foo() won't compile.

fun foo() {
throw IOException()
}

Java code calling bar() will compile.

@Throws(IOException::class)
fun bar() {
throw IOException()
}

Lambdas

If lambda is the last argument in a function we can move it out of the presences

list.any { i: Int -> i > 0 }

it or this can denote the argument if it's only one

list.any { it > 0 }

the last expression is the result

list.any {
println("processing $it")
it > 0 // is the same as return it > 0
}

we can destruct arguments

map.mapValues { (key, value) -> "$key -> $value!" }

example of storing a lambda in a variable of type (Int, Int) -> Int

val sum: (Int, Int) -> Int = { x, y -> x + y }

Bound reference store the object on which the member can delay to called, while unbound can be called on any object of a given type

Bound

class Person(val name: String, val age: Int) {
fun isOlder(ageLimit: Int) = age > ageLimit

fun getAgePredicate() = this::isOlder // <- here is a bound reference
}

Unbound

fun isEven(i: Int): Boolean = i % 2 == 0
::isEven

return in lambdas

A return inside a fun would return from the whole function

fun duplicateNonZero(list: List<Int>): List<Int> {
return list.flatMap {
if (it == 0) return listOf()
listOf(it, it)
}
}

println(duplicateNonZero(listOf(3, 0, 5))) // would return [] - an empty list

We can specify what lambda to return from so that we do not return from the whole function (@)

fun duplicateNonZero(list: List<Int>): List<Int> {
return list.flatMap {
if (it == 0) return@flatMap listOf()
listOf(it, it)
}
}

The labeled return inside a forEach actually corresponds to java's continue

list.forEach {
if (it == 0) return@forEach
println(it)
}

is the same as

for (element in list) {
if (element == 0) continue
print(element)
}

Extensions

Kotlin's extensions are basically static functions defined in a separate auxiliary class. We can't call private members from extensions. As extension functions are static under the hood they cannot be overridden. Properties can be extended.

fun String.lastChar() = this[this.length - 1]

Reference of the coding convention

... when defining extension functions for a class which are relevant for all clients of this class, put them in the same file with the class itself. When defining extension functions that make sense only for a specific client, put them next to the code of that client. Avoid creating files just to hold all extensions of some class.

Inline functions

Inlining a function means that compiler will substitute a body of the function instead of calling it. No anonymous class will be created for it.

inline fun <R> run(block: () -> R): R = block()

val name = "Kotlin"
run { println("Hi, $name!") }

Will be compiled to the bytecode

val name = "Kotlin"
println("Hi, $name!") // inlined code of lambda body

Resource management with use

java

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

static String readFirstLineFormFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}

kotlin

import java.io.BufferedReader
import java.io.FileReader

fun readFirstLineFromFile(path: String): String {
BufferedReader(FileReader(path)).use { br ->
return br.readLine()
}
}

Nullability

Kotlin took the approach of making NPE a compile-time exception. Each type is a child of the same nullable type. Under the hood fun foo(): String? = "foo" is


@Nullable
public static final String foo() {
return "foo";
}

fun bar(): String = "bar" is


@NotNull
public static final String foo() {
return "foo";
}

Operators to work with nullability in Kotlin

!!

s!! - throws NPE if s is null

?.

?:

as?

Generics

A regular generic argument (T for example) can receive a nullable type. We can make it explicit.

fun <T> List<T>.firstOrNull(): T? {}

At the same time we can set a non-nullable upper bound with Any.

fun <T : Any> foo(list: List<T>) {
for (element in list) {

}
}

If we need to use multiple constraints for a type parameter we must use the where.

fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable {
if (!seq.endsWith('.')) {
seq.append('.')
}
}

In situations when generics result in the same JVM signature we must use @JvmName to resolve the conflict.

fun List<Int>.average(): Duble { }
@JvmName("averageOfDouble")
fun List<Double>.average(): Double { }

Collections and Sequences