Kotlin

Lunch & Learn

Created by Erik Nelson

What is Kotlin?

A statically-typed JVM languaged developed by JetBrains

  • Development started in 2010
  • Current release: 1.0 Beta 4 (Dec. 22, 2015)

Design Goals

  • To create a Java-compatible language,
  • That compiles at least as fast as Java,
  • Make it safer than Java, i.e. statically check for common pitfalls such as null pointer dereference,
  • Make it more concise than Java by supporting variable type inference, higher-order functions (closures), extension functions, mixins and first-class delegation, etc;
  • And, keeping the useful level of expressiveness (see above), make it way simpler than the most mature competitor – Scala.

Features

  • Concise syntax
  • Compiles to JVM bytecode or Javascript
  • Interoperates seamlessly with Java
  • Null safety
  • A lot more!

Cool Minor Features

  • Package name is independent of directory structure
  • Elvis operator
  • No checked exceptions

Things not covered in this presentation

(But worth mentioning)

  • Generics
  • Destructuring declarations
  • Type-safe builders
  • Operator overloading

Syntax


package hello

fun main(args: Array) : Unit {
   println("Hello World!")
}
                        

or more simply:


package hello

fun main(args: Array) {
   println("Hello World!")
}
                        

Class Syntax


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

fun main(args: Array) {
   Greeter(args[0]).greet()
}
                        

Variables

Immutable variables


val a: Int = 1
val b = 1   // `Int` type is inferred
val c: Int  // Type required when no initializer is provided
c = 1       // definite assignment
                        

Mutable variables


var x = 5 // `Int` type is inferred
x += 1
                        

Lambda Syntax

Abbreviated form:


val sum = { x: Int, y: Int -> x + y }
                        

Full syntactic form:


val sum: (Int, Int) -> Int = { x, y -> x + y }
                        

One param lambda:


ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'
                        

Declaring a higher-order function


fun  lock(lock: Lock, body: () -> T): T {
  lock.lock()
  try {
    return body()
  }
  finally {
    lock.unlock()
  }
}
                        

Control Flow

Most of these structures should be familiar from Java, with some noted improvements

If Expression

In Kotlin if is an expression (it returns a value)


// Traditional usage
var max = a
if (a < b)
  max = b

// With else
var max: Int
if (a > b)
  max = a
else
  max = b

// As expression
val max = if (a > b) a else b
                        

if branches can be blocks. In this case the last expression is the value of the block.


val max = if (a > b) {
    print("Choose a")
    a
  }
  else {
    print("Choose b")
    b
  }
                        

For Loop


for (arg in args) {
    print(arg)
}
                        

While Loop


while (i < args.size){
    print(args[i++])
}
                        

When Expression

The when expression replaces the switch operator of C-style languages


fun cases(obj: Any) {
  when (obj) {
    1          -> print("One")
    "Hello"    -> print("Greeting")
    is Long    -> print("Long")
    !is String -> print("Not a string")
    else       -> print("Unknown")
  }
}
                        

String Templates


val i = 10
val s = "i = $i" // evaluates to "i = 10"
                    

expressions are supported via curly braces:


val s = "abc"
val str = "$s.length is ${s.length}" // "abc.length is 3"
                    

Ranges

Check if a number is in a range:


if (x in 1..y-1)
  print("OK")
                        

Or out of range:


if (x !in 1..y-1)
  print("OUT")
                        

Iterating over a range:


for (x in 1..5)
  print(x)
                        

Collections

Iterating over a collection:


for (name in names)
  println(name)
                        

Check if a collection contains an object


if (text in names) // names.contains(text) is called
  print("Yes")
                        

Filter and map with lambda expressions:


names
    .filter { it.startsWith("A") }
    .sortedBy { it }
    .map { it.toUpperCase() }
    .forEach { print(it) }
                        

Data Classes

Generates the following methods for free:

  • equals()/hashCode()
  • toString()
  • copy()

Example:


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

Null Safety

Regular variables can not be null:


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

To allow nulls, declare the variable as a nullable type:


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

Another Example

Property access:


var a: String = "abc"
val l = a.length // ok
                        

Compiler error if you try to access a nullable:


var b: String? = "def"
val l = b.length // error: variable 'b' can be null
                        

How to work with nullable types

Checking for null


val l = if (b != null) b.length else -1
                        

The compiler tracks null checks, which allows for:


if (b != null && b.length > 0)
  print("String of length ${b.length}")
else
  print("Empty string")
                        

* Only works when b is immutable

Safe Calls

Recall this:


var b: String? = "def"
val l = b.length // error: variable 'b' can be null
                        

How do we get around this? The safe operator:

?.


var b: String? = "def"
val l = b?.length
                        

returns b.length if b is not null, otherwise returns null

Chaining Safe Calls


bob?.department?.head?.name
                        

This call will return null if any of the properties in the chain are null

Elvis Operator


val l: Int = if (b != null) b.length else -1
                        

The above can be rewritten more simply:


val l = b?.length ?: -1
                        

Advanced example:


fun foo(node: Node): String? {
  val parent = node.getParent() ?: return null
  val name = node.getName() ?: throw IllegalArgumentException("name expected")
  // ...
}
                        

!! Operator

If your life just feels too empty without NPEs...


val l = b!!.length()
                        

This will return the length if b has value, otherwise will throw a NullPointerException

Extensions

Allow you to extend a class with new functionality without having to inherit from the class or use any type of design pattern such as a Decorator.

Two kinds of extensions:

  • Extension functions
  • Extension properties

No more Util classes!

Extension Functions

To declare an extension function, its name must be prefixed with a receiver type (the type being extended)


fun MutableList.swap(index1: Int, index2: Int) {
  val tmp = this[index1] // 'this' corresponds to the list
  this[index1] = this[index2]
  this[index2] = tmp
}
                        

This can now be used on any MutableList<Int>:


val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'
                        

Extension Properties


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

Extensions don't insert members into classes, so initializers are not allowed. Getters/setters must be used.

Further Reading