Kotlin

Topics related to Kotlin:

Getting started with Kotlin

Kotlin is a statically-typed object-oriented programming language developed by JetBrains primarily targeting the JVM. Kotlin is developed with the goals of being quick to compile, backwards-compatible, very type safe, and 100% interoperable with Java. Kotlin is also developed with the goal of providing many of the features wanted by Java developers. Kotlin's standard compiler allows it to be compiled both into Java bytecode for the JVM and into JavaScript.

Compiling Kotlin

Kotlin has a standard IDE plugin for Eclipse and IntelliJ. Kotlin can also be compiled using Maven, using Ant, and using Gradle, or through the command line.

It is worth noting in $ kotlinc Main.kt will output a java class file, in this case MainKt.class (Note the Kt appended to the class name). However if one was to run the class file using $ java MainKt java will throw the following exception:

Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
    at MainKt.main(Main.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 1 more

In order to run the resulting class file using Java, one must include the Kotlin runt-time jar file to the current class path.

java -cp .:/path/to/kotlin/runtime/jar/kotlin-runtime.jar MainKt

Extension Methods

Extensions are resolved statically. This means that the extension method to be used is determined by the reference-type of the variable you are accessing; it doesn't matter what the variable's type is at runtime, the same extension method will always be called. This is because declaring an extension method doesn't actually add a member to the receiver type.

Java 8 Stream Equivalents

About laziness

If you want to lazy process a chain, you can convert to a Sequence using asSequence() before the chain. At the end of the chain of functions, you usually end up with a Sequence as well. Then you can use toList(), toSet(), toMap() or some other function to materialize the Sequence at the end.

// switch to and from lazy
val someList = items.asSequence().filter { ... }.take(10).map { ... }.toList()

// switch to lazy, but sorted() brings us out again at the end
val someList = items.asSequence().filter { ... }.take(10).map { ... }.sorted()

Why are there no Types?!?

You will notice the Kotlin examples do not specify the types. This is because Kotlin has full type inference and is completely type safe at compile time. More so than Java because it also has nullable types and can help prevent the dreaded NPE. So this in Kotlin:

val someList = people.filter { it.age <= 30 }.map { it.name }

is the same as:

val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }

Because Kotlin knows what people is, and that people.age is Int therefore the filter expression only allows comparison to an Int, and that people.name is a String therefore the map step produces a List<String> (readonly List of String).

Now, if people were possibly null, as-in a List<People>? then:

val someList = people?.filter { it.age <= 30 }?.map { it.name }

Returns a List<String>? that would need to be null checked (or use one of the other Kotlin operators for nullable values, see this Kotlin idiomatic way to deal with nullable values and also Idiomatic way of handling nullable or empty list in Kotlin)

Reusing Streams

In Kotlin, it depends on the type of collection whether it can be consumed more than once. A Sequence generates a new iterator every time, and unless it asserts "use only once" it can reset to the start each time it is acted upon. Therefore while the following fails in Java 8 stream, but works in Kotlin:

// Java:
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("b"));

stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception
// Kotlin:  
val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }

stream.forEach(::println) // b1, b2

println("Any B ${stream.any { it.startsWith('b') }}") // Any B true
println("Any C ${stream.any { it.startsWith('c') }}") // Any C false

stream.forEach(::println) // b1, b2

And in Java to get the same behavior:

// Java:
Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
          .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

Therefore in Kotlin the provider of the data decides if it can reset back and provide a new iterator or not. But if you want to intentionally constrain a Sequence to one time iteration, you can use constrainOnce() function for Sequence as follows:

val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }
        .constrainOnce()

stream.forEach(::println) // b1, b2
stream.forEach(::println) // Error:java.lang.IllegalStateException: This sequence can be consumed only once. 

See also:

Interfaces

Generics

Implied Upper Bound is Nullable

In Kotlin Generics, the upper bound of type parameter T would be Any?. Therefore for this class:

class Consumer<T>

The type parameter T is really T: Any?. To make a non-nullable upper bound, explicitly specific T: Any. For example:

class Consumer<T: Any>

Functions

Null Safety

Idioms

Enum

Just like in Java, enum classes in Kotlin have synthetic methods allowing to list the defined enum constants and to get an enum constant by its name. The signatures of these methods are as follows (assuming the name of the enum class is EnumClass):

EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>

The valueOf() method throws an IllegalArgumentException if the specified name does not match any of the enum constants defined in the class.

Every enum constant has properties to obtain its name and position in the enum class declaration:

val name: String
val ordinal: Int

The enum constants also implement the Comparable interface, with the natural order being the order in which they are defined in the enum class.

Reflection

Reflection is a mechanism to introspect language constructs (classes and functions) at the runtime.

When targeting JVM platform, runtime reflection features are distributed in separate JAR: kotlin-reflect.jar. This is done to reduce runtime size, cut unused features and make it possible to target another (like JS) platforms.

Configuring Kotlin build

Conditional Statements

In contrast to Java's switch, the when statement has no fall-through behavior. This means, that if a branch is matched, the control flow returns after its execution and no break statement is required. If you want to combine the bahaviors for multiple arguments, you can write multiple arguments separated by commas:

when (x) {
    "foo", "bar" -> println("either foo or bar")
    else -> println("didn't match anything")
}

Loops in Kotlin

In Kotlin, loops are compiled down to optimized loops wherever possible. For example, if you iterate over a number range, the bytecode will be compiled down to a corresponding loop based on plain int values to avoid the overhead of object creation.

logging in kotlin

Annotations

Class Inheritance

Arrays

Vararg Parameters in Functions

Basic Lambdas

Input type parameters can be left out when they can be left out when they can be inferred from the context. For example say you have a function on a class that takes a function:

data class User(val fistName: String, val lastName: String) {
    fun username(userNameGenerator: (String, String) -> String) =
        userNameGenerator(firstName, secondName)
}

You can use this function by passing a lambda, and since the parameters are already specified in the function signature there's no need to re-declare them in the lambda expression:

val user = User("foo", "bar")
println(user.userName { firstName, secondName ->
     "${firstName.toUppercase}"_"${secondName.toUppercase}"
 }) // prints FOO_BAR

This also applies when you are assigning a lambda to a variable:

//valid:
val addition: (Int, Int) = { a, b -> a + b }
//valid:
val addition = { a: Int, b: Int -> a + b }
//error (type inference failure):
val addition = { a, b -> a + b }

When the lambda takes one parameter, and the type can be inferred from the context, you can refer to the parameter by it.

listOf(1,2,3).map { it * 2 } // [2,4,6]

Type-Safe Builders

A type-safe builder is a concept, rather than a language feature, so it is not strictly formalized.

A typical structure of a type-safe builder

A single builder function usually consists of 3 steps:

  1. Create an object.
  2. Execute lambda to initialize the object.
  3. Add the object to structure or return it.

Type-safe builders in Kotlin libraries

The concept of type-safe builders is widely used in some Kotlin libraries and frameworks, eg.:

  • Anko
  • Wasabi
  • Ktor
  • Spec

Kotlin Caveats

JUnit

Exceptions

Strings

Regex

Collections

Type aliases

Type aliases is a feature of the compiler. Nothing is added in the generated code for the JVM. All aliases will be replaced by the real type.

Kotlin Android Extensions

Visibility Modifiers

DSL Building

Kotlin for Java Developers

Ranges

RecyclerView in Kotlin

Singleton objects

Delegated properties

Class Delegation

Basics of Kotlin

  1. Kotlin file has an extension .kt.
  2. All classes in Kotlin have a common superclass Any, that is a default super for a class with no supertypes declared(similar to Object in Java).
  3. Variables can be declared as val(immutable- assign once) or var(mutables- value can be changed)
  4. Semicolon is not needed at end of statement.
  5. If a function does not return any useful value, its return type is Unit.It is also optional. 6.Referential equality is checked by the === operation. a === b evaluates to true if and only if a and b point to the same object.

coroutines