About Kotlin

  • Created by JetBrains in 2011
  • Object-Oriented
  • Functional Programming

Hello World

class Greeter(val name : String) {
    fun greet() {
        println("Hello, ${name}")
    }
}

fun main(args : Array<String>) {
    Greeter("World").greet()
}

Classes and members

class Greeter(private val name : String) {
    private var counter = 0

    fun getGreeting(includeNumber: Boolean): String {
        return if (includeNumber) {
            "Hello, ${name}. Greeting message nr ${counter++}"
        } else {
            "Hello, ${name}"
        }
    }
}

fun main(args : Array<String>) {
    Greeter("World").getGreeting(true)
}

Default and named arguments

Kotlin has both default and named arguments. This allows class definitions that are concise and precise

data class Person(val name: String, val country: String = "Norway", val age : Int = 18)

So, creating a new Person where you're fine with the default country, but want to change the age?

Person(name = "Arne Scheie", age = 73)

A person from Norway of age 18 could be created by

Person("Some name")

Whereas an empty constructor would give a compile error

Person()
>>> error: no value passed for parameter 'name'        
    

Same thing in Java

Java version of the same would've looked like this. equals, hashCode, getters excluded for some brevity

public Person {
    final String name;
    final String country;
    final Integer age;
    public Person(String name) {
        this(name, "Norway", 18);
    }
    public Person(String name, String country) {
        this(name, country, 18);
    }
    public Person(String name, Integer age) {
        this(name, "Norway", age);
    }
    public Person(String name, String country, Integer age) {
        this.name = name;
        this.country = country
        this.age = age;
    }
}

Lambdas

val verboseLambda : (Int) -> Boolean =
        { i : Int ->
            println("lambda invoked")
            i % 2 == 0
        }
val result = verboseLambda(4)

// type inference
val tinyNumbers = listOf(1,2,3,4).filter{ n -> n < 3 }

// single argument sugar
val conciseLambda = listOf(1,2,3).filter{ it > 0}

Nullable Types I

var a: String = "abc"
a = null // compilation error

var b: String? = "abc"
b = null

Nullable Types II

val a: String? = null

if (a != null) {
    print("String of length ${a.length}") //good
}

val length: Int = a!!.length // NPE

val length2: Int? = a?.length // good

Nullable Types III

data class SmallCompany(var employee: Person? = null)
data class Person(var middleName: String? = null)

val middleName: Int? =
            SmallCompany().employee?.middleName?.length // null

Smart Casts

fun smartCast1(x: Any) {
    if (x is String) {
        print(x.length) // x is automatically cast to String
    }
}


fun smartCast2(x: Any) {
    if (x !is String) return
    print(x.length) // x is automatically cast to String
}

When-expression (Pattern Matching)

fun ofType(x: Any) {
    when (x) {
        is String -> println(x.length) // Smart casts work for when-expressions
        in 1..10 -> println("x is in the range 1..10")
        is IntArray -> println(x.sum())
        else -> println("x is unknown")
    }
}

Object Expressions and Declarations I

// Object Expression ~= anonymous inner classes in Java
window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }
})

Object Expressions and Declarations II

class Car(val color = "blue") // Class

object Lock { // Object declaration
    val i = 3
}

println(Car().color)
println(Lock.i)

Object Expressions and Declarations III

class MyClass {
    companion object {
        fun create(): MyClass = MyClass()
    }
}

MyClass.create() // access as if "static" in class

Extension functions

Kotlin allows you to write custom extensions for any class. This is a power that needs to be used carefully, or you'll end up in extension hell; Kotlin's version of implicits hell

fun List<Int>.largerThanN(n: Int) = this.filter { it > n }
listOf(5, 7, 8, 11, 1, 2, 3).largerThanN(5)
>>> [7, 8, 11]
            

Extension properties

Kotlin also supports extension properties.

val <T> List<T>.lastIndex: Int
get() = size - 1

listOf(1,2,3,4,5).lastIndex
>>> 4
            

Collections I - Immutability

Kotlin's collections are immutable by default, so default constructors returns an immutable collection

val x = listOf(1)
x.add(2)
>>> error: unresolved reference: add
val y = x + 2
>>> y: [1,2]
x
>>> x: [1]
            

Collections II - Mutable

If you want a mutable list you'll have to explicitly ask for it, and a mutable list works much like java's. The add method returns a boolean saying whether or not the object was added

val x = mutableListOf(1)
>>> [1]
x.add(2)
x
>>> [1,2]
            

Collections III - Functional

They also support the usual functional collection suspects

val exampleList = listOf(1,2,3,4,5)                
val x = exampleList.reduce { acc, el -> acc + el }                
>>> x: Int
x
>>> 15

val (evens, odds) = listOf(1, 2, 3, 4).partition { it % 2 == 0 }
evens
>>> [2,4]
odds
>>> [1,3]

Collections IV - Reduced verbosity

Java groupBy

Map<Gender, List<String>> groupedBy = roster.stream()
    .collect(
    Collectors.groupingBy(
        Person::getGender,                      
        Collectors.mapping(
        Person::getName,
        Collectors.toList()))
        );

Kotlin groupBy

val groupedBy: Map<Gender, List<String>> = roster.groupBy(
    keySelector = { it.gender },
    valueTransform = { it.name }
)