Concise notes for the impatient learner

Uncategorized

Review (or Learn…) Kotlin in One Hour

This post is a quick Kotlin reference. If you know Kotlin already, refresh your memory! If you are familiar with other programming languages, this should get you coding in Kotlin right away. For a more detailed coverage and advanced topics, refer to recommended reading section.

You can try the code at Kotlin Playground. Alternatively, you can use an IDE, such as IntelliJ IDEA or Eclipse.

In the examples, fun main() {} is skipped to be more concise. To run most examples, enclose the last lines (other than the function/class declarations) in fun main() {}.

1. Overview

Many times we’ll compare Kotlin to Java. Kotlin keywords are in italic, while Java keywords not present in Kotlin are in quotes.

Main similarities with Java

  • Statically typed
  • Several of the constructs have the same syntax
  • Fully interoperable (Kotlin code can call Java code, and vice versa)

Main differences from Java

  • A significant part of the syntax (in general, Kotlin is designed to be more concise)
  • Support for first-level functions

Note that the semicolon at the end of a line can be omitted in Kotlin.

Kotlin has a concept of package like Java. The import statement can be used for classes and functions.
Kotlin allows multiple classes per file, and the file can have any name.
The directory structure doesn’t have to follow package naming.

2. Variables

To declare a variable, you have two options

  • var: mutable reference
  • val: immutable reference (like “final” in Java)
// Declare, then assign
var a: Int  // no initializer, must specify type
a = 10

// Declare and assign
var b: Int = 10  // specify type 
var c = 10       // use type inference

Assignments are statements in Kotlin (they are expressions in Java).
Variables can be outside a class (top-level property). This is equivalent to a static field in Java.

Unlike Java, there is no differentiation between primitive and reference types. All types are objects.
Basic types (corresponding to Java primitive types)

  • Byte, Short, Int, Long
  • Float, Double
  • Char
  • Boolean

Kotlin does not convert numbers between types automatically. You need to explicitly use methods such as toLong(), toFloat()
Special types:

  • Any: root of class hierarchy, similar to Object in Java.
  • Unit: like void in Java.
  • Nothing: return type for functions that never return (e.g., they always throw exception).

2.1 Strings

Strings are similar to Java, and there are many library functions to manipulated them. Kotlin supports string templates, where you can can refer to variables in the string (using $ or ${}).

var text = "some text"
print("Content: $text")  // prints "Content: some text"

You can also use triple-quoted strings (using “””) to avoid escaping any character. These strings can even include line breaks.

3. Conditions and Loops

3.1 Conditions

if is an expression, not a statement (it has a value). When using multiple expressions in curly braces, the value is given by the last expression.

var a = 10  // enter a value

// Return absolute value of variable a
var abs = if (a >= 0) a else -a

// Multiple conditions
if (a > 0)
    print("positive")
else if (a < 0) {
    // statements...
}

The when construct is similar to “switch” in Java, but it is also an expression. There is no need to use “break” between conditions. Conditions can be any object, not just constants.

var fruit = "Apple"  // enter a value
when (fruit) {
    "Apple" -> print("This is an apple")
    "Orange" -> print("This is an orange")
    else -> print("This is a different fruit")
}

3.2 Loops

while loops are the same as in Java.

while(condition) { }

do {
} while(condition)

for loops are like a for-each loop over a range (similar to the enhanced for loop in Java).

for (i in 1..10)          // .. operator creates ranges (endpoint is included)
for (i in 1 until 10)     // exclude endpoint
for (i in 1..10 step 2) 
for (i in 10 downTo 1)

4. Functions

A function is declared with the fun keyword.

// Function with block body
fun sum(a: Int, b: Int): Int {
    return a + b
}

// Function with expression body (return type inferred)
fun sum(a: Int, b: Int) = a + b

Kotlin supports named arguments (Java does not).

fun printFullName(first: String, last: String) {
    print(first + " " + last)
}

printFullName(last = "Smith", first = "John")  // prints "John Smith"

Kotlin also support default parameter values (Java does not), which reduce the need for overloads.

fun concatenate(str1: String = "1", str2: String = "2", str3: String = "3"): String {
    return str1 + str2 + str3
}

println(concatenate("a", "b", "c"))  // prints "abc"
println(concatenate())               // prints "123"

Variadic functions are declared with vararg. Arguments are packed into an array.

fun sum(vararg values: Int) = values.sum()  // use sum() method of Array

println(sum(1,2,3))  // prints "6"

Functions can be outside a class (top-level functions). This is equivalent to a static function in Java.
You can nest functions (local functions). Local functions have access to parameters and variables of the enclosing function.

// True if all arguments are even.
fun allEven(vararg values: Int): Boolean {
    fun isEven(value: Int) = value % 2 == 0
    
    for (value in values)
        if (!isEven(value)) return false
    return true
}

println(allEven(1,2))  // prints "false"
println(allEven(2,4))  // prints "true"

5. Classes

Class declarations are similar to Java. There is no “new” keyword to instantiate.

5class Person {
    var firstName: String = ""
    var lastName: String = ""

    fun getFullName() = firstName + " " + lastName
}

var person = Person()
person.firstName = "John"
person.lastName = "Smith"
print(person.getFullName())  // prints "John Smith"

5.1 Constructors

A primary constructor is defined in parentheses after the class name. It consists of a list of parameters.

class Person(_firstName: String, _lastName: String) {
    val firstName = _firstName
    val lastName = _lastName

    fun getFullName() = firstName + " " + lastName
}

var person = Person("John", "Smith")
print(person.getFullName())  // prints "John Smith"

Parameters declared with val/var create properties with the same name without the need to declare them and assign them. Default parameter values can be defined like in other functions.

class Person(val firstName: String, val lastName: String) {
    fun getFullName() = firstName + " " + lastName
}

var person = Person("John", "Smith")
print(person.getFullName())  // prints "John Smith"

The code above omits the constructor keyword before the parameter declaration. You could also write

class Person constructor(val firstName: String, val lastName: String)

A primary constructor cannot contain code. Code must be placed in initializer blocks.

class Person(var firstName: String, var lastName: String) {
    init {
        firstName = firstName.capitalize()
        lastName = lastName.capitalize()
    }

    fun getFullName() = firstName + " " + lastName
}

var person = Person("john", "smith")
print(person.getFullName())  // prints "John Smith"

You can declare secondary constructors inside the class using the constructor keyword. val/var are not allowed on parameters. Any assignment to properties must be explicit.

class Person {
    constructor(firstName: String, lastName: String) { // code }
}

Another constructor of the same class can be called with this(). If you don’t declare a constructor, a default one that does nothing is created automatically.

5.2 Final/Open/Abstract

Unlike Java, classes, methods, and properties are final by default. Use the open modifier

  • on classes, to allow inheritance;
  • on methods and properties, to allow overriding (similar to “virtual” in C++).

Like in Java, you can declare a class or method abstract. Abstract members are always open.

When overriding an open method, the override keyword is mandatory.

5.3 Visibility

The visibility modifiers are similar to Java (public, protected, private). However, visibility is public by default in Kotlin. Private can be used on top-level declarations to make them visible only in the file where they are declared.

5.4 Inner and Nested Classes

Like in Java, there are two types of class inside another class

  • inner class: it has a reference to the outer class
  • (static) nested class: it has no reference to the outer class

In Java, a class declared inside another class is inner by default. Using the “static” modifier makes it a nested class.
In Kotlin, a class inside another class is nested by default . The inner modifier turns it into an inner class.

If you use the sealed modifier on a class, all subclasses must be nested in the superclass (or at least in the same file after Kotlin 1.1). This is not available in Java.

5.5 Properties

Property = field + accessors (getter/setter)

  • The syntax for accessors is similar to C#. They are declared with get() and set().
  • If not declared, default accessors are created automatically (they just read/write the field).
  • get() can be used to retrieve the value of a backing field or to calculate a value from other fields or properties.
// The circle needs to store only the value of radius. 
// It exposes an area property that is calculated from radius.
class Circle {
    var radius: Double = 0.0
    var area: Double
        get() = Math.PI * radius * radius
        set(value) {
            radius = Math.sqrt(value/Math.PI)
        } 
}

val circle = Circle()
circle.radius = 2.0
print(circle.area)

A special identifier field is available to access the backing field. The compiler automatically generates the backing field if you refer to field or if you use default accessors.

By default, the accessors have the same visibility of the property. You can add a modifier before get() or set() to customize visibility.

5.6 Inheritance

A colon replaces Java’s “extends”. You may need to provide the superclass constructor parameters (this is similar to the syntax of C++ member initializer lists).

class Student(val firstName: String, val lastName: String) : Person(firstName, lastName) 
    // code
}

A superclass constructor can also be called with super(). For example, in a secondary constructor

class Student: Person {
    constructor(firstName: String, lastName: String) : super(firstName, lastName) {
        // code
    }
}

5.7 Infix notation

For functions with one parameter, you can place the function name between the target object and the parameter. The function must have the infix modifier. The example below uses a string template to refer to variables inside a string (using $).

class Number(val value: Int) {
    infix fun countTo(toValue: Int) {
        for (i in value..toValue)
            print("$i ")
    }
}

val number = Number(2)
number countTo 5  // output: 2 3 4 5

5.8 Extension Functions

An extension function can be called like a member of class, even though it is defined outside of it. Within the extension function, you can refer to the object you called the function on with this, as usual. You still don’t get access to private or protected members of the class (encapsulation is preserved).

fun SomeClass.extFunction(): Int { 
    ...
}

var obj = SomeClass()
obj.extFunction()

5.9 Enum Classes

To declare an enum, use enum class syntax.

enum class Level {
    HIGH,
    LOW
}

print(Level.HIGH)  // prints "HIGH"

Like in Java, enum classes can contain other members in addition to values. If present, you need to pass constructor parameters to create enum values.

enum class Level(val setting1: Int, val setting2: Int) {
    HIGH(1, 1),
    LOW(0, 0)
}

print(Level.HIGH)           // prints "HIGH"
print(Level.HIGH.setting1)  // prints "1"

5.10 Data Classes

If you add the data modifier to a class, the compiler provides default implementations of the universal object methods (equals, hashCode, toString). The implementations automatically take into account the values of all properties. A copy method is also generated, which makes it easier to clone instances. These features are very useful for classes designed to contain data.

6. Interfaces

The syntax is similar to Java

  • Declaration with interface keyword.
  • Support for default method implementations (added in Java 8). Java requires the “default” keyword, Kotlin has no special keyword.

There are a few differences from Java

  • The “implements” keywords in Java is replaced by a colon. A class can implement multiple interfaces, but extend only one class (same as Java).
  • The override modifier is mandatory (“@Override” annotation is optional in Java).
  • An interface can contain property declarations (an abstract class is needed in Java).
interface Printable {
    fun print()
}

class Item(val description: String) : Printable {
    override fun print() = println(description)
}

Item("MyItem").print()  // prints "MyItem"

7. Exceptions

The syntax and concepts are similar to Java

  • throw keyword to raise an exception (“new” is not needed, as with any other constructor). throw is an expression.
  • try/catch/finally construct to handle exceptions. try is an expression.

A few things are different from Java

  • No differentiation between checked and unchecked exceptions.
  • As a consequence, the “throws” keyword in function signatures is not used.

8. Delegation

Class delegation makes it easier to implement the decorator pattern. When a class implements an interface, you can delegate the implementation of the interface to another object inside the class. You just need to override the methods you want to change compared to that object. This saves a lot of boilerplate code. Delegation is specified through the by keyword.

interface MyInterface {
    fun incr(num: Int): Int
    fun decr(num: Int): Int
}

class MyObject : MyInterface {
    override fun incr(num: Int) = num+1
    override fun decr(num: Int) = num-1
}

class MyDecorator(val myObject: MyObject = MyObject()) : MyInterface by myObject {
    override fun incr(num: Int) = num+2  // override incr, but automatically use myObject for decr
}


val myObject = MyObject()
println(myObject.incr(1))  // prints "2"
println(myObject.decr(1))  // prints "0"
    
val myDecorator = MyDecorator()
println(myDecorator.incr(1))  // prints "3"
println(myDecorator.decr(1))  // prints "0"

Kotlin also supports delegated properties, which allow delegation of access to a property to another object. The topic is outside the scope of this post.

9. Object keyword

The object keyword has several uses.
You can use an object declaration to create a singleton.

object MySingleton {
    var content = 0
}

print(MySingleton.content)

A companion object has similar use as Java static methods and fields (as there is no “static” keyword in Kotlin).

class MyClass {
    companion object {
        var name = "MyClass"
        fun myFun() = "MyFun" 
    }
}

println(MyClass.name)
println(MyClass.myFun())

You can also create anonymous objects, similar to Java anonymous inner classes. A new instance is created every time the object expression is executed.

interface MessageGenerator {
    fun getMessage(): String
}

fun printMessage(holder: MessageGenerator ) = print(holder.getMessage())


printMessage(object : MessageGenerator {
    override fun getMessage() = "Message"
})

10. Lambdas

The syntax for lambdas is similar to Java 8. You can treat a lambda like any other value (assign to a variable or use as a parameter).

var greater = { a: Int, b: Int -> a > b }
println(greater(3, 2))  // prints "true"

When used as an argument, there are several syntax simplifications allowed that reduce verbosity.

// Function that takes another function as parameter
fun applyTo2(mathFun: (Int) -> Int) {
    println(mathFun(2))  // execute passed function with argument 2 and prints result
}

applyTo2({ x: Int -> x*2})  
applyTo2() { x: Int -> x*2 }  // move lambda outside parentheses (if it's last argument)
applyTo2{ x: Int -> x*2 }     // omit parentheses (if lambda is the only argument)
applyTo2{ x -> x*2 }          // omit parameter type (if it can be inferred)
applyTo2{ it*2 }              // omit parameter name (if only one parameter). Default "it" name provided automatically

Kotlin also supports lambdas with receivers. If one of the parameters of the lambda is an object, you can call the lambda on a receiver object (similar to an extension function) and the object will be available inside the lambda.

// Use regular lambda
fun applyToNum(num: Int, mathFun: (Int) -> Int) {
    println(mathFun(num))
}
applyToNum(2, { x: Int -> x*2})  

// Use lambda with receiver
fun applyToNum(num: Int, mathFun: Int.() -> Int) {
    println(num.mathFun())  // can call lambda on receiver object
}
applyToNum(2, { this*2 } )  // receiver object accessible through "this"

11. Generics

The syntax is similar to Java for the most part.

// generic class with type parameter T
class Container<T> {  
    // generic functions with type parameter T
    fun <T> put(item: T) { /* code */ }
    fun <T> get(): T { /* code */ }
}

You can use type parameter constraints to restrict the type arguments allowed.

// String is upper bound. Subclasses of String are allowed. Equivalent to "T extends String" in Java.
class StringContainer<T: String>   

// Using Any as upper bound forbids use of null type arguments. To allow them, use Any? as upper bound.
class NonNullContainer<T: Any>

Kotlin also allows us to specify variance on type parameters. This is useful to establish subtype relationships between the parameterized types based on the relationship of the type arguments (for example, is Container<Int> a subtype of Container<Any>?) An explanation of variance is outside the scope of this post. Please refer to the recommended reading section.

12. Collections

Kotlin uses Java collection classes. There are two types of collection interface in Kotlin

  1. Read-only collections (Collection interface)
  2. Mutable collections (MutableCollection interface, which implements Collection and adds methods to modify the content of the collection)

Java collection interfaces have both a read-only and mutable interface in Kotlin. For example, for the List interface

  • List implements Collection
  • MutableList implements MutableCollection and List

There is no special syntax for arrays, and arrays are just classes (Array<T>). By default, arrays contain object types. Special classes are provided for arrays of primitive (not boxed) types. For example, Array<Int> is like Integer[] in Java, while IntArray is like int[].

Kotlin provides functions to create collections, such as listOf(), mutableListOf() and similarly for Set, Map, and Array.

Collections support functional programming passing lambdas. Functions such as map and filter take lambdas as arguments and operate on collections.
You can use sequences to avoid creating results for intermediate operations. Evaluation is deferred until a terminal operation is encountered (lazy evaluation like Java 8 Streams).

The for loop is convenient to iterate over collections.

val list = listOf("item0", "item1")
for (value in list) {
    print("$value,")  // prints "item0,item1,"
}
for ((index, value) in list.withIndex()) {
    print("$index:$value,")  // prints "0:item0,1:item1,"
}

val map = mapOf("key0" to "item0", "key1" to "item1")
for ((key, value) in map) {
    print("$key:$value,")  // prints "key0:item0,key1:item1,"
}

13. Operators

Operators are similar to Java. One important exception are the equality operators

  • == in Kotlin compares using the equals method (Java requires call to equals)
  • === (identity equals) in Kotlin compares references (same as == operator in Java)

Kotlin allows overloading operators by implementing functions with specific names (from a predefined set of operators) preceded by the operator keyword.

data class School(val teachers: Int, val students: Int) {
    operator fun plus(school: School) : School {
        return School(teachers + school.teachers, students + school.students)
    }
} 

val school1 = School(1, 20)
val school2 = School(2, 30)
print(school1 + school2)  // prints "School(teachers=3, students=50)"

The type of the second operand and of the return value may differ from the type of first operand. Kotlin also allows overloading compound assignment operators, unary operators, comparison, index, range, and destructuring operators (refer to the documentation for more details).

Destructuring declarations are worth an extra look. They are used to unpack values from a container class and assign multiple variables at once. The mechanism is based on component operators. For example, the Pair class supports the following functionality

var pair = Pair(1, 2) 
var (x, y) = pair  // x is assigned 1, y is assigned 2

14. Nullability

If a variable can be null, it must be explicitly stated with ? after the type. If a type doesn’t have a ?, it doesn’t allow null values. Java supports a similar concept with the Optional type or annotations. However, Kotlin enforces the check by default, while Java requires the explicit use of Optional or annotations.

var nonNullStr: String = null     // not allowed (compile-time error). String is non-nullable type.
var mayBeNullStr: String? = null  // allowed. String? is nullable type.

When using a nullable type, Kotlin makes it mandatory to check for null values where it’s possible to have one. This ensures that errors are found at compile-time rather than at runtime (with null pointer exceptions).

fun toCaps(str: String?): String? {
    return str.toUpperCase()  // error: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
}

print(toCaps("test"))

To check for a null value, you can use

  • null check (with if)
    • if check passes, we know the variable cannot be null within the scope of the check.
  • Safe call operator ?.
    • To call a method on an object that may be null.
    • If the object is null, it returns null instead of calling the function.
    • Available in Java 8 as well.
  • Null-coalescing or Elvis operator ?:
    • Similar to ternary operator in Java with an implicit null check condition.
  • Not-null assertion !!
    • Converts variable to a non-nullable type.
    • If variable is null, it throws NullPointerException.
// null check
fun toCaps(str: String?): String {
    return if (str != null) str.toUpperCase() else ""
}

// Safe-call operator
fun toCaps(str: String?): String? {
    return str?.toUpperCase()  // note that the return type is nullable
}

// Null-coalescing operator
fun toCaps(str: String?): String {
    val nonNullStr = str ?: ""  // if str is null, nonNullStr = "", otherwise nonNullStr = str
    return nonNullStr.toUpperCase()
}

// Not-null assertion
fun toCaps(str: String?): String {
    val nonNullStr: String = str!!
    return nonNullStr.toUpperCase()
}

15. Annotations

The syntax to apply annotations is the same as for Java (@ character). The syntax to declare annotations is different. Java uses “@interface”, while Kotlin uses an annotation class.

annotation class MyAnnotation(val value: Int)  // annotation with one parameter

16. Reflection

Reflection works similarly to Java. To inspect the members of a class at runtime, get an instance of KClass and use the properties provided. The members are of type KCallable, whose subtypes are KFunctionN (where N is the number of parameters of the function) and KPropertyN (where N is the number of parameters of the getter).

annotation class MyAnnotation(val value: Int)  // annotation with one parameter

class MyClass {
    var myField: String = "Test"

    @MyAnnotation(3)
    fun myMethod(num: Int) {
        println(num)
    }
}

fun main() {
    val myobj = MyClass() 
    val kClass = myobj.javaClass.kotlin  // use .kotlin property to access the Kotlin reflection API
    println(kClass.simpleName)  // prints "MyClass"

    // The code from here on does not work in Kotlin Playground. Use an IDE instead.
    for (member in kClass.members) {  
        print(member.name + " ")  // prints "myField myMethod equals hashCode toString " (members of MyClas and parent Any)
    }

    val kFunction = kClass.members.elementAt(1)
    kFunction.call(myobj, 2)  // call myMethod on myobj with argument 2

    val annotation = kFunction.annotations.elementAt(0) 
    println(annotation)                                  // prints "@MyAnnotation(myValue=3)"
    println(annotation.annotationClass.simpleName)       // prints "MyAnnotation"
    println(annotation.annotationClass.members.elementAt(0).call(annotation))  // prints "3"
}

Recommended Reading

Kotlin in Action (ISBN: 1617293296)
Clear and comprehensive coverage of Kotlin. Similarities, differences, and interoperability with Java are clearly described across the book.

Leave a Reply