Busy Developer's Guide to Kotlin

ted@tedneward.com | Blog: http://blogs.tedneward.com | Twitter: tedneward | Github: tedneward | LinkedIn: tedneward

Kotlin

Objectives

Overview

What is Kotlin?

Overview

Kotlin is...

Getting Started

Installation, execution, first run, ...

Getting Started

Installation

Getting Started

Installation: IntelliJ

Getting Started

Installation: Command-line

Getting Started

Hello, Kotlin

fun main(args: Array<String>) {
    println("Hello, World!")
}

Getting Started

Compiling the standalone executable (command-line)

$ kotlinc hello.kt -include-runtime -d hello.jar
$ java -jar hello.jar
Hello World!
$

(This produces a "fat JAR"--includes all the Kotlin dependencies)

Getting Started

Compiling the standalone executable (command-line)

$ kotlinc hello.kt

(This produces HelloKt.class in the current directory)

$ kotlin HelloKt
Hello World!
$

(This uses the JVM to run HelloKt.)

Getting Started

Running kotlinc as a scripting tool

Code expected to start execution on line 1 (hello.kts):

println("Hello, World!")

$ kotlinc -script hello.kts

Getting Started

Lastly, Kotlin can be run as a REPL

Getting Started

Kotlin can also compile to JavaScript

Kotlin Basics

Syntax

Kotlin Basics

Syntax: C-family language based (loosely)

Kotlin Basics

Source files may begin with "package" declarations

Kotlin Basics

Source files may reference other modules

Primitive Types

The atoms of the language

Primitive Types

Primitive types

Primitive Types

Boolean

Primitive Types

Numbers

Primitive Types

Numbers

Primitive Types

Kotlin conversions

val a: Int = 10000
print(a === a) // Prints 'true'

val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!Prints 'false'!!!

val b: Byte = 1 // OK, literals are checked statically
// val i: Int = b // ERROR
val i: Int = b.toInt() // OK: explicitly widened

Primitive Types

Char: single character

Primitive Types

Strings: multiple-character

Primitive Types

Kotlin strings

val s = "Hello, world!\n"
val s2 = "s = ${s}!"

val text = """
  for (c in "foo")
    print(c)
"""

val quote = """
    |Tell me and I forget. 
    |Teach me and I remember. 
    |Involve me and I learn.
    |  (Benjamin Franklin)
    """.trimMargin()

Collection types

Bundling up

Collections

Arrays

Collections

Kotlin arrays

val nums = arrayOf(1, 2, 3) // [1, 2, 3]
println(nums[0])
//nums[1] = 12 // ERROR!
println(nums[2])

val nulls : Array<Any?> = arrayOfNulls(3) // [null, null, null]

val asc = Array(5, { i -> (i * i).toString() }) // [0,1,4,9,16]
println(asc)

Collections

Ranges

Collections

Kotlin ranges

val nums = 0..10 // 0,1,2,3,...,10
if (1 in nums)
  println("true!")

val desc = 10 downTo 0 // 10,9,8,...,0

val evens = 0..10 step 2 // 0,2,4,...,10
if (5 in evens)
  println("Errr... broken?")

val odds = 1..10 step 2 // 1,3,5,...,9
if (5 in odds)
  println("true!")

Collections

Lists

Collections

Kotlin lists

val items = listOf(1, 2, 3, 4)
items.first() == 1
items.last() == 4
val evens = items.filter { it % 2 == 0 }
println(evens == listOf(2, 4)) // true

val rwList = mutableListOf(1, 2, 3, 4)
if (rwList.none { it > 6})
  println("No item in rwList is greater than 6")

Collections

Sets

Collections

Kotlin sets

val numbers = hashSetOf(0, 1, 2, 2, 3, 3, 3, 4, 5, 4)
println(numbers) // [0, 1, 2, 3, 4, 5]

Collections

Maps

Collections

Kotlin maps

val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)
println(readWriteMap["foo"])  // prints "1"

val snapshot: Map<String, Int> = HashMap(readWriteMap)
println(snapshot)
//snapshot["foo"] = 12 // ERROR!

Flow Control

When decisions must be made

Flow Control

if (decision-branching)

Flow Control

Kotlin if

var a = 5
var b = 12

var max: Int = 0
if (a > b) 
  max = a 
else 
  max = b 
 
// As expression 
val max2 = if (a > b) a else b

Flow Control

when (decision-branching)

Flow Control

Kotlin when

val x = 2
when (x) {
  1 -> print("x == 1")
  2 -> print("x == 2")
  else -> { // Note the block
    print("x is neither 1 nor 2")
  }
}

Flow Control

when branch conditions

Flow Control

Kotlin when

val validNumbers = 1..10
when (x) {
  0, 1 -> print("x == 0 or x == 1")
  parseInt(s) -> print("s encodes x")
  in 1..10 -> print("x is in the range")
  in validNumbers -> print("x is valid")
  !in 10..20 -> print("x is outside the range")
  else -> print("otherwise")
}
    
val hasPrefix = when(y) {
  is String -> y.startsWith("prefix")
  else -> false
}

Flow Control

for (iteration/loops)

Flow Control

Kotlin for

val array = arrayOf(1, 2, 3, 4, 5, 6)
for (i in array)
  println(i)

for (idx : Int in array) {
  println(idx)
}

for (index in array.indices) {
  println(array[index])
}

for ((index,value) in array.withIndex()) {
  println("the array element at $index holds $value")
}

Flow Control

while, do/while (loops)

Flow Control

break

continue

return

Exceptions

For when the code goes south

Exceptions

Kotlin manages errors as Exceptions

Exceptions

Throwing an exception is simple

Exceptions

Guarded blocks frame exception-handling behavior

Exceptions

Kotlin Exceptions

val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }

Functions

Modularizing logic in the micro

Functions

Functions

Functions

Invoking Functions

Functions

Function declarations

fun add(left: Int, right: Int): Int {
  return left + right
}
val five = add(2, 3)
    
val adder = fun(left: Int, right: Int): Int {
  return left + right
}
val six = adder(3, 3)
    
val adderUp = fun(left: Int, right: Int): Int = left + right

Functions

Function parameter variations

fun doComplicatedThings(a: Int = 0, b: Int = -1, c: Boolean = false): Int {
  return if (c) a else b
}
doComplicatedThings() // = -1
doComplicatedThings(c = true) // = 0
    
fun log(msg: String, vararg msgs: String): Unit {
  print(msg)
  for (m in msgs) {
    print(" " + m)
  }
  println()
} 
log("Howdy!")
log("Howdy","y'all!")

Functions

Passing Functions

Functions

Shorthand (lambda) function syntax

Functions

Lambdas

val ints = arrayOf(4, 8, 16)
val doubled = ints.map { it -> it * 2 }
val halved = ints.map { it / 2 }
    
fun doSomethingNTimes(times: Int, body: () -> Unit) {
  for (i in 1..times) {
    body()
  }
}
doSomethingNTimes(5) {
  println("Whee!")
}

Classes

Cookie-cutters for object instances

Classes

class

Classes

Instantiating a class

Classes

Simple class and use

class Person(var firstName: String, val lastName: String, var age: Int)
{
  init {
    println("Person $firstName $lastName constructed")
  }
}
val ted = Person("Ted", "Neward", 45)
println("${ted.firstName} is ${ted.age} years old")

Classes

Classes can declare secondary constructors

Classes

Secondary constructors

class Person(var firstName: String, val lastName: String, var age: Int)
{
  constructor(soleName: String, age: Int) 
    : this(soleName, "", age)
  {
  }
}
val ted = Person("Ted", "Neward", 45)
println("${ted.firstName} is ${ted.age} years old")
val cher = Person("Cher", 39)
println("${cher.firstName} is ${cher.age} years old")

Classes

Classes can contain

Properties

Encapsulating state without too much ceremony

Properties

Properties represent state in a class

Properties

Simple property usage

public class Address { 
  public var name: String = ""
  public var street: String = ""
  public var city: String = ""
  public var state: String? = ""
  public var zip: String = ""
}
val addr = Address()
addr.name = "Fred Flintstone"
addr.street = "1 Bedrock Way"

Properties

Getter/setter usage

class SecretAsset {
  var codeName: String = ""
  var realName: String = ""
    get() = "<REDACTED>"
    set(value) {
      field = value
    }
}

Properties

Sometimes the implicit backing field isn't quite right

Properties

Backing properties

class Container {
  private var _table: Map<String, Int>? = null
  public val table: Map<String, Int>
    get() {
      if (_table == null)
        _table = HashMap()
      return _table ?: throw AssertionError("Set to null by another thread")
    }
}

Properties

Properties can be marked "const"

Properties

Properties can be marked "lazyinit"

Properties

Delegated properties

Properties

Using a delegating property

class Example {
    var p: String by Delegate()
}

Properties

Delegated type must expose two operator methods

Properties

A delegating property type

class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
  return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
  println("$value has been assigned to '${property.name} in $thisRef.'")
}
}

Properties

Out-of-the-box property delegate types

Properties

lazy

class Container {
  val lazyValue: String by lazy {
      println("computed!")
      "Hello"
  }
}
val c = Container()
println(c.lazyValue)
println(c.lazyValue)

Properties

observable

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}
    
val user = User()
user.name = "first"
user.name = "second"

Properties

map

class Person(val map: Map<String, Any?>) {
  val name: String by map
  val age: Int     by map
}
val person = Person(mapOf(
    "name" to "John Doe",
    "age"  to 25
))
println(person.name)
println(person.age)
println(person.map)

Methods and Operators

Enabling behavior on classes

Methods and Operators

Classes can have behavior

Methods and Operators

Methods

class FootballPlayer {
  fun run() { println("Running!") }
  fun pass() { println("Passing!") }
}
val tomBrady = FootballPlayer()
tomBrady.pass()
tomBrady.pass()
//tomBrady.cheat() // No! Bad Tom!

Methods and Operators

Methods can be marked with "operator"

Methods and Operators

Methods can be marked with "operator"

Methods and Operators

Methods can be marked with "operator"

Methods and Operators

Methods can be marked with "operator"

Methods and Operators

Operators

data class Money(val amount: Int, val currency: String = "USD") {
  operator fun plus(other: Money): Money {
    if (this.currency != other.currency)
      throw Exception("Conversion required!")
    else
      return Money(this.amount + other.amount, this.currency)
  }
}
operator fun Money.unaryMinus() = Money(-amount, currency)
    
val salary = Money(100000, "USD")
val bonus = Money(50000, "USD")
println(salary + bonus)

Methods and Operators

Member functions can be used with infix notation

Methods and Operators

Infix operations

class Matrix(var number : Int) {
  infix fun rotate(amt: Int): Matrix {
    return this
  }
}
var m = Matrix(5)
m = m rotate 5

Objects

What other languages call 'static'

Objects

Object declarations

Objects

Objects

object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
  // ...
}

val allDataProviders: Collection<DataProvider>
  get() = // ...
}
DataProviderManager.registerDataProvider(...)

Objects

Companion objects can be declared using "companion" keyword

Objects

Companion objects

class MyClass {
  companion object Factory {
    fun create(): MyClass = MyClass()
  }
}
val instance = MyClass.create()

class MyOtherClass {
  companion object {
  }
}
val companionInstance = MyOtherClass.Companion

Access Modifiers

Keeping private details, private

Access Modifiers

Access consists of four modifiers:

Access Modifiers

Package-level declarations

Access Modifiers

Class declarations

Inheritance

Modeling the IS-A relationship between classes

Inheritance

Kotlin supports single-class Inheritance

Inheritance

Simple inheritance

open class Base(p: Int)

class Derived(p: Int) : Base(p)

Inheritance

All classes implicitly extend base type "Any"

Inheritance

Type-testing/casting

Inheritance

Property overriding

Inheritance

Overriding properties

open class Person(val firstName: String, val lastName: String, val age: Int)
{
  open var salary: Int = 50000
}
    
class Actor(val name: String, var oscars: Int) :
  Person(name, "", 39)
{
  override var salary: Int
    get() { return 1000000 }
    set(value) { /* ignored */ }
}
    
val willSmith = Actor("Will", 0)
println(willSmith.salary)

Inheritance

Method overriding

Inheritance

Overriding properties

open class Base {
  open fun v() { }
  fun nv() { }
}
    
class Derived() : Base() {
  override fun v() { }
}

Inheritance

Classes and/or members can be abstract

Inheritance

Delegation

Inheritance

Delegation

interface Base {
  fun print()
}
class BaseImpl(val x: Int) : Base {
  override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
  val b = BaseImpl(10)
  Derived(b).print() // prints 10
}

Inheritance

Anonymous object derivative instances ("object expressions")

Inheritance

Object expressions

open class A(x: Int) {
  public open val y: Int = x
}
interface B { }

val ab: A = object : A(1), B {
  override val y = 15
}

Inheritance

Object expressions can also create "ad-hoc" objects

Inheritance

Ad-hoc object expressions

val adHoc = object {
  var x: Int = 0
  var y: Int = 0
}
print(adHoc.x + adHoc.y)

Interfaces

Enabling the promise (and delivery) of behavior

Interfaces

Interfaces are behavior-only markers

Interfaces

A simple interface

interface MyInterface {
    fun bar()
    fun foo() {
      // optional body
    }
}

Interfaces

Classes inherit from interfaces

Interfaces

Interfaces can contain properties

Interfaces

Interface with properties

interface MyInterface {
    val property: Int // abstract
    val propertyWithImplementation: String
        get() = "foo"
    fun foo() {
        print(property)
    }
}

class MyClass : MyInterface {
    override val property: Int = 29
}

Data classes

DTOs written into the language

Data classes

Some classes just want to be named bundles of data

Data classes

Data classes out-of-the-box support:

Data classes

Data classes

data class Money(var amount: Int, val type: String)
val salary = Money(100000, "USD")
println("Your salary is ${salary.type}${salary.amount}")
val euroSalary = salary.copy(type = "EUR")
println("In Europe, you would make ${euroSalary}")

Enumerated types

Bounded type-safe lists of possible values under a convenient name

Enums

Basic usage

Enums

Basic enums

enum class Direction {
  NORTH, SOUTH, EAST, WEST
}
val heading = Direction.NORTH
println(heading)

Enums

Enums can also be initialized with values

Enums

Valued enums

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}
val r = Color.RED
println(r.rgb)

Enums

Enums can also declare anonymous (sub) classes

Enums

Anonymous subclass enums

enum class ProtocolState {
  WAITING {
    override fun signal() = TALKING
  },
    
  TALKING {
    override fun signal() = WAITING
  };
    
  abstract fun signal(): ProtocolState
}
var state = ProtocolState.WAITING
println(state) //  WAITING
state = state.signal()
println(state) // TALKING

Sealed Classes

Kotlin's answer to the discriminated union

Sealed Classes

Sealed classes are a closed inheritance family

Sealed Classes

Sealed classes start with base class marked "sealed"

Sealed Classes

A sealed expression library

sealed class Expr {
    class Const(val number: Double) : Expr()
    class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
}
fun eval(expr: Expr): Double = when(expr) {
    is Expr.Const -> expr.number
    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
    Expr.NotANumber -> Double.NaN
    // the `else` clause is not required because we've covered all the cases
}
val expr = Expr.Sum(Expr.Const(2.0), Expr.Const(2.0))
println("${expr.e1} + ${expr.e2} = ${eval(expr)}") // 4.0

Generics/Parameterized Types

Generics

Kotlin supports parameterized types

Generics

Simple generics example

class Box<T>(t: T) {
    var value = t
}
    
val box1: Box<Int> = Box<Int>(1)
val box2 = Box(1) // 1 is-a Int, so box2 must be a Box<Int>

Generics

Functions can also have type parameters

Generics

Simple generic functions

fun <T> singletonList(item: T): List<T> {
    // ...
}
    
fun <T,U> swap(params: Pair<T,U> ): Pair<U,T> {
    return Pair(params.second, params.first)
}

Annotations

Custom metadata elements

Annotations

Annotations are metadata for code

Annotations

Usage

Annotations

Annotation definition

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}
    
class InFoo @Inject constructor(dependency: MyDependency) {
    // ...
    var x: MyDependency? = null
        @Inject set
}
    
@Special("Because it's a Foo too!") class FooTwo {}
    
val f = @Suspendable { Fiber.sleep(10) }

Annotations

Annotation definition

@file:JvmName("Example")
    
class Example(@field:Ann val foo,    // annotate Java field
              @get:Ann val bar,      // annotate Java getter
              @param:Ann val quux)   // annotate Java constructor parameter

Annotations

Definition

Annotations

Annotation definition

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
        AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy
    
annotation class Special(val why: String)

Reflection

Examining the code at runtime

Reflection

Kotlin Reflection is access to code elements by name without invocation

Reflection

Function composition in Kotlin

fun isOdd(x: Int) = x % 2 != 0
fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"
    
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // prints [1, 3]
    // since numbers is a list of Int, the IsOdd(Int) is selected
    
// Function composition
fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
    return { x -> f(g(x)) }
}
fun length(s: String) = s.length
    
val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")
    
println(strings.filter(oddLength)) // Prints "[a, abc]"

Dynamic

For when you want anything to be able to do anything

Dynamic

Dynamic is a type that represents a dynamic type

Dynamic

Dynamic is a type

Summary

Kotlin is...

Credentials

Who is this guy?