On Github erikthered / kotlin-lunch-and-learn
Created by Erik Nelson
A statically-typed JVM languaged developed by JetBrains
package hello fun main(args: Array) : Unit { println("Hello World!") }
or more simply:
package hello fun main(args: Array) { println("Hello World!") }
class Greeter(val name: String) { fun greet() { println("Hello, $name") } } fun main(args: Array) { Greeter(args[0]).greet() }
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
var x = 5 // `Int` type is inferred x += 1
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'
A lambda expression is always surrounded by curly braces, parameter declarations in the full syntactic form go inside parentheses and have optional type annotations, the body goes after an -> sign. If we leave all the optional annotations out, what’s left looks like this:
It’s very common that a lambda expression has only one parameter. If Kotlin can figure the signature out itself, it allows us not to declare the only parameter, and will implicitly declare it for us under the name it:
Note that if a function takes another function as the last parameter, the lambda expression argument can be passed outside the parenthesized argument list.
fun lock(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } }
Let’s examine the code above: body has a function type: () -> T, so it’s supposed to be a function that takes no parameters and returns a value of type T. It is invoked inside the try-block, while protected by the lock, and its result is returned by the lock() function.
If we want to call lock(), we can pass another function to it as an argument (see function references)
Or we can pass a lambda expression
Most of these structures should be familiar from Java, with some noted improvements
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 (arg in args) { print(arg) }
while (i < args.size){ print(args[i++]) }
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") } }
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"
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)
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) }
Generates the following methods for free:
Example:
data class User(val name: String, val age: Int)
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
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
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
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
bob?.department?.head?.name
This call will return null if any of the properties in the chain are null
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") // ... }
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
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:
No more Util classes!
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'
val List.lastIndex: Int get() = size - 1
Extensions don't insert members into classes, so initializers are not allowed. Getters/setters must be used.