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 Worldn"
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:
- File.writeText(text: String): Sets the content of this file as text.
- File.writeBytes(array: ByteArray): Sets the content of this file as an array of bytes. If this file already exists, it becomes overwritten.
- File.printWriter(): Returns a new PrintWriter for writing the content of this file.
- File.bufferedWriter(): Returns a new BufferedWriter for writing the content of this file.
java.io.File("/tmp/test.json").writeText("""{"message": "Hello World!"}""")
Reading Files
Kotlin provides extension methods to java.io.File to read files:
- File.readText(): Gets the entire content of this file as a String using UTF-8.
- File.ReadLines(): Reads the file content as a list of lines.
- File.forEachLine(): Reads this file line by line.
- File.readBytes(): Gets the entire content of this file as a byte array.
- File.bufferedReader(): Returns a new BufferedReader for reading the content of this file.
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.