Kotlin Notes
Table of Contents
- Table of Contents
- Basics
- Collections
- OOP
- Nulls and Exceptions
- Lambdas and Higher-Order Functions
- References
Basics
-
Kotlin requires one
main
per app:1 2 3
fun main(args: Array<String>) { // the args part can be omitted println("Hello Kotlin!") }
-
Shorter
if
1 2
println(if (x > y) "x is greater" else "x is not greater") return if (x > y) x else y
-
var
vsval
:- When using
var
, we can assign another value to the variable. - When using
val
, the reference to the object stays forever. However, if the variable is an array, the array itself can be updated. (Similar tolet
in Swift)
- When using
-
Primitive types are also objects controlled by references.
-
Similar to Swift, we can specify the type:
1 2
var z: Int = 6 var x: Long = z.toLong() // similarly, toFloat(), toByte(), etc.
-
Arrays:
1 2 3
var myArray = arrayOf(1, 2, 3) var myLength = myArray.size var explicitArray: Array<Int> = arrayOf(1, 2, 3)
-
Strings:
1 2 3 4 5 6 7 8 9 10
var x = 42 var myString = "x is $x" // When accessing a property or function of a object, use ${} var myArray = arrayOf(1, 2, 3) var arraySize = "The size is ${myArray.size}" var firstItem = "The first item is ${myArray[0]}" "12.345-6.A".split(".", "-") // splits at both . and - // We don't need to escape for triple quote strings for regular expressions. val regex = """(.+)/(.+)\.(.+)""".toRegex() // We can also use triple quotes for multiline strings like in Python.
-
Functions;
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// We can have default values like in Python. fun foo(bar: Int = 1): Int { // Unit means no return value, or just omit it. // And Nothing means the function never returns. // stuff return 1 } var result = foo(1) fun max(a: Int, b: Int): Int = if (a > b) a else b // also works fun listOf<T> (vararg values: T): List<T> {...} // vararg makes it variadic. // And we need to explicitly unpack the array when passing: listOf("args:", *args)
-
Loops:
1 2 3 4 5
for (x in 1..100) println(x) // end inclusive for (x in 1 until 100) println(x) // not end inclusive for (x in 15 downTo 1) println(x) // end inclusive for (x in 1..100 step 2) println(x) for (item in items) println(item)
-
Input:
1
val userInput = readLine()
-
when
:1 2 3 4 5 6 7 8 9
when (x) { 0 -> println("It's 0") 1, 2 -> println("It's 1 or 2") else -> { println("It's not 0.") println("It's not 1 nor 2.") } } // We can also use when without the argument, then each case needs to be a Boolean.
-
We can use the qualified
this
to access thethis
from an outer scope:this@MainActivity
. -
How to compile and run in the command line:
1
kotlinc main.kt -include-runtime -d main.jar && java -jar main.jar "optional args"
Collections
-
List
,Set
,Map
,MutableList
,MutableSet
,MutableMap
. -
listOf()
,mutableListOf()
.mList.set(1, "foo")
,.shuffle()
,.last()
,.max()
. -
mapOf(0 to 'a', 1 to 'b', 2 to 'c')
for ((key, value) in mMap)
. (to
actually creates aPair<K, V>
). -
Add
out
(<out T>
) to make the generics covariant (use a subtype when a supertype is expected) - achieving polymorphism (like<? extends E>
in Java). Addin
to make it contravariant - the opposite of covariance (use a supertype when a subtype is expected) (like<? super E>
in Java). Producer (read-only) out, consumer in (write-only).1 2 3 4 5 6 7 8
fun <T> copy(src: List<out T>, dst: MutableList<in T>) { for (x in src) { dst.add(x) } } val src = listOf<Int>(1, 2, 3) val dst = mutableListOf<Any>() copy<Number>(src, dst)
-
We can use
in
to check existence just like in Python. -
Like
enumerate()
in Python, we have.withIndex()
:1
for ((index, element) in collection.withIndex()) {...}
OOP
-
Example class:
1 2 3 4 5 6 7 8 9
class Dog(val name: String, var weight: int, val breed: String = "default") { var temperament: String = "" // All properties must be initialized. // Or: // lateinit var temperament: String fun bark() { println("Woof!") } }
-
Custom getters and setters:
1 2 3 4 5 6 7 8
... val weightInKgs: Double get() = weight / 2.2 var weight = weightParam set(value) { if (value > 0) field = value // field is a keyword } ...
-
In fact, the complier adds getters and setters automatically:
1 2 3 4 5
var myProperty: String get() = field set(value) { field = value }
-
Classes, variables, and methods are final by default. To enable inheritance and overriding, add
open
before them. We also need to addoverride
to variables. -
The
init {}
blocks are called during initialization. -
If a property is defined using
val
in the superclass, we must override it in the subclass if we want to assign a new value to it. However, if it’s defined usingvar
, we just need to reassign it in theinit {}
block without usingoverride
. -
abstract
for classes, variables, and methods. These properties must all be overridden later. -
An
interface
lets us define common behavior OUTSIDE a superclass hierarchy. (Not IS-A, but share a property.) A class can have multiple interfaces, but can only inherit from a single direct superclass.class X: A, B {}
(No parentheses.class X: A()
means we are inheriting.) -
Use
is
to check the type:if (animal is Wolf)
. And useas
for explicit casting. -
Any
is the superclass of everything. -
Add
data
to the start of the class to make it behave like astruct
in Swift. Then we can use==
or.equals
to test the equivalence. (Equal objects have the same.hashCode()
value.) (And.toString()
returns the value of each property.) (BTW,===
is used for referential check (identity).) -
For
data
objects, we have destructuring declaration:1 2 3 4 5
val (title, number) = r // is equivalent to val title = r.component1 val number = r.component2 // And this can be used to return multiple values from a function, or we can use Pair.
-
Named arguments (
var r = Recipe(title = "title", foo = "bar")
) are also available like in Swift. -
Secondary constructors:
1
constructor(foo: Boolean) : this(0, foo) {} // Calls the primary constructor.
-
enum
class:1 2 3 4 5 6 7 8 9 10
enum class Color { RED, YELLOW, BLUE } fun foo(color: Color) = // Returns the value directly. when (color) { Color.RED -> 1 Color.YELLOW -> 2 Color.BLUE -> 3 }
-
Everything is
public
by default. We also haveprivate
,protected
, andinternal
(for a module). -
Operator overloading:
1 2 3 4 5
data class Point(val x: Int, val y: Int) { operator fun plus(other: Point): Point { return Point(x + other.x, y + other.y) } }
Nulls and Exceptions
-
Use
?
for nullable objects just like in Swift. -
w?.eat()
is a safe call. It’s only called when w is not null. -
Like optional binding in Swift, use
let
:1 2 3
w?.let { // executed when w is not null }
-
The Elvis operator
?:
1
w?.hunger ?: -1 // if null, return -1
-
!!
is like!
in Swift. It throws a NullPointerException if the value is null:w!!.hunger
. -
Like Java, the same
try catch finally
block for exception handling. And the samethrow
. But we can usetry
as an expression, which returns the value to the variable:1 2 3 4 5 6
val number = try { Integer.parseInt(reader.readLine()) } catch (e: NumberFormatException) { return // Or null if we want the flow to continue. } println(number)
-
as?
is the conditional cast, which returnsnull
is the casting is not possible:1
val foo = bar as? Person ?: return false
-
.filterNotNull()
returns a list with null filtered out.
Lambdas and Higher-Order Functions
-
We can assign a lambda to a variable:
1 2 3 4
val addFive: (Int) -> Int = {x: Int -> x + 5} val result = addFive.invoke(1) // 6 // or val result = addFive(1)
-
{it + 5}
is equivalent to{x -> x + 5}
. -
Lambdas can also be passed to functions.
1 2 3 4
fun convert(x: Double, converter: (Double) -> Double) : Double { return converter(x) } println(convert(1, {c: Double -> c * 1.8 + 32}))
-
.min()
and.max()
work with basic types:val mMax = mList.max()
-
minBy {}
andmaxBy {}
work with all types:val mMaxQuantity = groceries.maxBy {it.quantity}
. AlsosumBy {}
andsumByDouble {}
:mMap.values.sumBy {it}
. We can also use::
reference like in Java 8 (::foo
calls the top-level functionfoo
). -
filter {}
:val pricesOver3 = groceries.filter {it.price > 3.0}
. AlsofilterNot {}
. We can also use.count()
instead of.filter().size
. -
map {}
:val doubleInts = ints.map {it * 2}
-
forEach {}
:groceries.forEach {println(it.name)}
-
Closure means that a lambda can access any local variable that it captures (the variables declared in the outer scope). Closures are functions that are aware of the surroundings.
1 2 3 4 5
var sum = 0 ints.filter { it > 0 }.forEach { // sum is captured sum += it }
-
groupBy {}
returns aMap<Key, List>
(Key depends on the key’s type). -
fold {}
:val sumOfInts = ints.fold(0) {mSum, item -> mSum + item}
-
all
andany
:1
listOf(Person("Alice", 27), Person("Bob", 31)).all {it.age < 28}
-
flatMap {}
andflatten()
:1 2 3 4 5 6 7 8
val books = listOf(Book("Thursday Next", listOf("Jasper Fforde")), Book("Mort", listOf("Terry Pratchett")), Book("Good Omens", listOf("Terry Pratchett","Neil Gaiman"))) println(books.flatMap { it.authors }.toSet()) // [Jasper Fforde, Terry Pratchett, Neil Gaiman] // Or use .flatten() when we don't do any transformation: listOfLists.flatten()
-
Like a Python generator or Java stream, Kotlin also has the lazy sequence:
1 2 3 4
people.asSequence() .map(Person::name) .filter {it.startsWith("A")} .toList() // Converts the sequence back.
-
For SAM (Single Abstract Method) interface, we can pass a lambda instead of implementing a class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
fun interface IntPredicate { fun accept(i: Int): Boolean } val isEven = IntPredicate {it % 2 == 0} // Instead of val isEven = object : IntPredicate { override fun accpet(i: Int): Boolean { return i % 2 == 0 } } println(isEven.accept(8)) // Another example: button.setOnClickListener { view -> ... }