×
Learning Kotlin A lovely language for the JVM

Learning Kotlin - A lovely language for the JVM

Kotlin is a statically typed programming language designed by Jetbrains, the creator of the IntelliJ IDE platform. The language was first released in 2016, and since then has received a lot of traction. Kotlin is used as a backend language, as a front-end language and on the mobile platform, specifically Android. Kotlin is evolving at an enormous pace, and will be available on all platforms, from JVM, JavaScript to native. Time to learn us some Kotlin!

What is Kotlin

Kotlin is a multi-platform, multi paradigm, generic programming language that comes with several compilers. Using the Kotlin compiles, code can compile to JVM bytecode to be run on the Java Virtual Machine. There is also a JavaScript compiler, so code can run in browsers or the NodeJS runtime. There is also a LLVM compiler to compile Kotlin to a static binary.

The Kotlin syntax is heavily inspired by Scala to make the language very concise but lacks features that make Kotlin very easy to learn. By cutting out advanced language features, the language becomes more pragmatic and usable for the general public. Kotlin is generally seen as a friendly language with just enough abstractions that make Kotlin suitable for day-to-day use.

The balance of just-enough type-inference, functional abstractions, multi-platform integration and speed is why the language is loved so much. While the syntax is not compatible with Java, the JVM implementation of the Kotlin standard library is designed to interoperate with Java code and libraries. Kotlin uses type inference to reduce language verbosity and supports a full range of functional language features and advanced concurrency primitives.

Some assumptions

Before we move on, I will assume that you have a Mac, setup with homebrew and have experiences with JVM languages like Java or Scala. Most of the examples will be executed using the REPL.

Installing Kotlin

On a Mac, Kotlin can be installed by typing the following.

$ brew install kotlin

$ kotlin -version
Kotlin version 1.3.10-release-253 (JRE 1.8.0_172-b11)

$ kotlinc -version
info: kotlinc-jvm 1.3.10 (JRE 1.8.0_172-b11)

$ kotlinc-jvm -version
info: kotlinc-jvm 1.3.10 (JRE 1.8.0_172-b11)

$ kotlinc-js -version
info: kotlinc-js 1.3.10 (JRE 1.8.0_172-b11)

REPL

Kotlin comes with a Read Evaluate Print Loop (REPL):

$ kotlinc
Welcome to Kotlin version 1.3.10 (JRE 1.8.0_172-b11)
Type :help for help, :quit for quit
>>> :help
Available commands:
:help                   show this help
:quit                   exit the interpreter
:dump bytecode          dump classes to terminal
:load <file>            load script from specified file

>>> listOf(1, 2, 3).map { it + 2 }.map { it * 2 }.filter { it > 6 }.sum()
18

>>> :quit

Learning Kotlin

There are a lot of resources to learn Kotlin. Kotlin provides a A lot of resources to learn Kotlin. The Kotlin documentation is great! Because Jetbrains is supporting Kotlin, there is also a Kotlin Educational Plugin to start getting hands-on with Kotlin. There are a lot of books available for learning Kotlin. The JetbrainsTV YouTube Channel contains a lot of videos about Kotlin. The KotlinConf 2018 videos are available at the same channel. Apart from all the online learning resources, Jetbrains offers a course on Coursera.

Gradle Build Tool

Kotlin can be build with any tool because the compilers are available as command-line tools but the preferred way to build Kotlin programs is to use Gradle. To learn how to use Gradle, read my previous blog Learning Gradle - An Open Source Build Automation Tool.

Data types

Kotlin supports the following basic data types:

val a: Double = 2.0
val b: Float = 2.0f
val c: Long = 1L
val d: Int = 1
val e: Short = 1
val f: Byte = 1
val g: Int = 0x0F
val h: Int = 0b00001011
val i: Char = 'a'
val j: Boolean = true
val k: UInt = 1u
val l: UShort = 1u
val m: UInt = 1u
val n: ULong = 1u
// octal is not supported
println("$a $b $c $d $e $f $g $h $i $j $k $l $m $n")

Strings

Kotlin has the following String representations:

val a: String = "foo"
val b: String = "bar" + "baz"
val c: String = "Hello World\n"
val d: String = """Multi
                Line
                String"""
val e: String = """
    |Lorem ipsum dolor amet tousled coloring book pickled pug church-key, disrupt PBR&B schlitz celiac.
    |Taxidermy pork belly celiac, shabby chic drinking vinegar seitan etsy retro banjo listicle ramps.
    |Paleo blog shoreditch, taxidermy gastropub bespoke yuccie hexagon hammock sartorial ethical everyday
    |carry typewriter messenger bag cliche.
""".trimMargin()

String interpolation:

val a = 1
val b = 2
val c = a + b
val d = "$a + $b = $c"

Functions

Kotlin supports functions:

fun double(x: Int): Int {
    return x + 1
}
val x = double(1)

Lambdas and anonymous functions: A lambda expression is always surrounded by curly braces. In Kotlin, there is a convention that if the last parameter of a function accepts a function, a lambda expression that is passed as the corresponding argument can be placed outside the parentheses. If a function has a single argument, the function parameter is available under the name it. The type of it is inferred by the compiler.

fun g(x: Int, f: (Int) -> Int): (Int) -> Int {
    // return a lambda
    return { y -> f(x) + y }
}

fun h(x: Int, f: (Int) -> Int): (Int) -> Int {
    // return an anonymous function
    return fun (y: Int): Int { return f(x) + y }
}

    val a = g(2) { x -> x + 5}
    val a = g(2) { it + 5} // single argument
    val b = a(10)

    val c = h(2) { x -> x + 5}
    val c = h(2) { it + 5} // single argument
    val d = c(10)
    println("$b $d")

Package and Imports

Like Java, uses packages and imports to partition and group functions and avoid name collisions. A Kotlin source file starts with a package declaration, followed up by one or more import statements.

package baz.bar

import foo.Bar
import foo.Bar as fBar
import foo.*

fun foo() {}
class Bar

Every Kotlin file imports the following packages by default:

  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collections.*
  • kotlin.comparisons.*
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*
  • java.lang.*
  • kotlin.jvm.*

The import keyword can import classes, top-level functions and properties and enum constants.

Classes

Kotlin support defining classes:

class Person(val name: String, val age: Int) {
    fun walk(): Unit {
        println("$name is walking")
    }
}

val p = Person("Dennis", 42)
p.walk()

Data Classes

Kotlin supports defining data classes, which are classes that exist only to hold data. The generic concept of data classes is knows as ‘records’. The compiler automatically creates an ‘equals()/hashCode() pair’, a ‘toString()’, ‘componentN()’ functions corresponding to the properties in their order of declaration and a ‘copy()’ function.

data class Person(val name: String, val age: Int)

val dennis = Person("Dennis", 42)
val (name, age) = dennis
println("$name $age $dennis")

Control Flow

Kotlin provides control flows, which are expressions ie. they return a value.

val x = 2
val y = if (x > 1) x else 1
println(y)

Kotlin provides when expressions. For developers coming from Scala, it is not pattern matching. There is no decomposition so you cannot decompose data classes.

val x = 2
val y = when {
    x > 1 -> x
    else -> 1
}
println(y)

Error Handling

To handle errors, Kotlin uses the try-catch-finally syntax. Kotin does not have checked exceptions which is great!

val x: Int = try {
    1/0
} catch (e: ArithmeticException) {
    println(e)
    0
} finally {
    0
}
println(x)

Null Safety

Kotlin eliminates null references by means of the elvis operator.

Kotlin adds the following operators:

  • ?.: performs a safe call (calls a method or accesses a property if the receiver is non-null)
  • ?:: takes the right-hand value if the left-hand value is null (the elvis operator)
val x: String? = null // define a nullable type
val y = if (x != null) x.length else -1
val z = x?.length ?: -1
println("$y $z")

Collections

Kotlin provides List, Set and Map.

val xs: List<Int> = listOf(1, 2, 3).map { it + 2}
println(xs)
xs.forEach { println(it) }
val ys: List<Int> = setOf(1, 1, 2, 2, 3, 3).map { it + 2}
println(ys)
ys.forEach { println(it) }
val zs: List<Int> = xs.flatMap { x -> ys.map { it + x }}
println(zs)
zs.forEach { println(it) }
val kv: Map<String, String> = hashMapOf("name" to "Dennis", "age" to 42).mapValues { it.toString() }
kv.forEach { k, v -> println("k=$k, v=${v.javaClass}")}

Range

Kotlin provides ranges:

for (i in 1..10 step 2) println(i)
for (i in 10 downTo 1 step 2) println(i)
for (i in (1..10).reversed()) println(i)
for (i in 1.rangeTo(10)) println(i)
for (i in (1..10).map { it + 1 }.filter {it % 3 == 0}) println(i)

Extension Methods

Extension methods allow extending existing classes with new functionality without having to inherit from the class or use any type of design pattern such as Decorator. Create a function with the name of the type, like Int, and type the name of the new function. The value of the type is available with the reference this in the function. To create a generic extension method, use a generic type T and add the new function.

fun Int.sayMyName(): String {
    return "Dennis"
}

fun Int.sayMyNameAndAge(): String {
    return "Dennis $this"
}

fun Int.sayMyNameAndAdd(x: Int): String {
    return "Dennis ${this + x}"
}

fun <T : Any> T.whoAmI(): String {
    return "Dennis and I am ${this.javaClass}"
}

println(42.sayMyName())
Dennis
println(42.sayMyNameAndAge())
Dennis 42
println(42.sayMyNameAndAdd(10))
Dennis 52
println(listOf(1, 2, 3).whoAmI())
Dennis and I am class java.util.Arrays$ArrayList

Functional Programming

Functional data structures, patterns and typeclasses can be added to the standard library of Kotlin by means of the Arrow library. Arrow is open source and available on github

Add the following to build.gradle.kts:

dependencies {
    // Use the Kotlin JDK 8 standard library
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    // add arrow
    val arrow_version = "0.8.1"
    compile("io.arrow-kt:arrow-core:$arrow_version")
    compile("io.arrow-kt:arrow-syntax:$arrow_version")
    compile("io.arrow-kt:arrow-typeclasses:$arrow_version")
    compile("io.arrow-kt:arrow-data:$arrow_version")
    compile("io.arrow-kt:arrow-instances-core:$arrow_version")
    compile("io.arrow-kt:arrow-instances-data:$arrow_version")
    kapt("io.arrow-kt:arrow-annotations-processor:$arrow_version")

    // Use the Kotlin test library
    testImplementation("org.jetbrains.kotlin:kotlin-test")

    // Use the Kotlin JUnit integration
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}

Use the functional data structures:

import arrow.core.Try
import arrow.instances.list.foldable.fold
import arrow.instances.monoid

val xs = listOf(1, 2, 3)
val x = xs.fold(Int.monoid())
println(x)

val y = Try { 1/0 }
val z: Int = y.fold({-1}){it}
println(z)

Concurrency

Coroutines are a Kotlin feature that convert async callbacks for long-running tasks, such as database or network access, into sequential code. For good introduction to coroutines I advice watching Introduction to Coroutines by Roman Elizarov

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1")
}

Example: Hello World

import kotlinx.coroutines.*

fun main(args: Array<String>) {
    GlobalScope.launch {
        // launch new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

Example: Launching 10.000 coroutines. The application runs for 5 seconds, and all coroutines are finished.

import kotlinx.coroutines.*

suspend fun main(args: Array<String>) {
    val jobs = List(10000) {
        GlobalScope.launch {
            delay(5000)
            print(".")
        }
    }
    jobs.forEach { it.join() }
}

Random Number Generator

The following generates random numbers:

// generate a random number
for (x in 1..10) println((1..10).random())

// generate the same random number sequence every time
import kotlin.random.Random
val rnd = Random(1)
for (x in 1..10) println(rnd.nextInt(10))

UUID

The JDK standard library can generate a UUID.

import java.util.UUID

val uuid = UUID.randomUUID()

Writing Files

Kotlin provides extension methods to java.io.File to write files:

java.io.File("/tmp/test.json").writeText("""{"message": "Hello World!"}""")

Reading Files

Kotlin provides extension methods to java.io.File to read files:

val text = java.io.File("/tmp/test.json").readText()
println(text)

JSON Encoding

Moshi is a JSON library that has support for Kotlin data classes. To setup Moshi add:

dependencies {
    implementation("com.squareup.moshi:moshi-kotlin:1.8.0")
    kapt("com.squareup.moshi:moshi-kotlin-codegen:1.8.0")
}

An example:

import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory

@JsonClass(generateAdapter = true)
data class Person(val name: String, val age: Int)

val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

val personAdapter = moshi.adapter(Person::class.java)
val dennis = Person("Dennis", 42)
val json  = personAdapter.toJson(dennis)
println(json)
val dennisFromJson = personAdapter.fromJson("""{"name":"Dennis","age":42}""")
println(dennisFromJson)

Conclusion

Kotlin is a very nice programming language. The language syntax and features reminds me of the Groovy and Scala programming language and maybe the best comparison would be that it is a mix of the two languages. Kotlin is available for both the JVM, Android platform and there is a Kotlin native project to make Kotlin available for systems programmers.

A feature that I miss is pattern matching - a mechanism for checking a value against a pattern. It is not possible in Kotlin to match data classes against a pattern. Pattern matching is a great feature that will hopefully be added to the language.

Kotlin does not provide implicits nor implicit resolution, a feature that can really clean up code when code is processing data like with JSON or AVRO serialization.

The Arrow library provides functional data structures and type classes for those who cannot live without Semigroups, Monoid, Functors, Monads and such.

What I really like about Kotlin are coroutines, a Kotlin feature that convert async callbacks for long-running tasks, such as database or network access, into sequential code.

For projects that don’t need the heavy lifting of Scala, but want the concise syntax and flexibility of a modern programming language on the JVM, then I would advice Kotlin.

Picture of Dennis Vriend
Dennis Vriend
Cloud Consultant