Kotlin Notes
Table of Contents
- Table of Contents
- Basics
- Collections
- OOP
- Nulls and Exceptions
- Lambdas and Higher-Order Functions
- References
Basics
-
Kotlin requires one
mainper app:1 2 3fun main(args: Array<String>) { // the args part can be omitted println("Hello Kotlin!") } -
Shorter
if1 2println(if (x > y) "x is greater" else "x is not greater") return if (x > y) x else y -
varvsval:- 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 toletin Swift)
- When using
-
Primitive types are also objects controlled by references.
-
Similar to Swift, we can specify the type:
1 2var z: Int = 6 var x: Long = z.toLong() // similarly, toFloat(), toByte(), etc. -
Arrays:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17var myArray = arrayOf(1, 2, 3) var myLength = myArray.size var explicitArray: Array<Int> = arrayOf(1, 2, 3) val fixedArray = IntArray(26) // 26 0s val fixedArray1 = IntArray(26) { 1 } // 26 0s val allZero: Boolean = fixedArray.all { it == 0 } fixedArray[0]++ fixedArray[0]-- // 2D array val m = 3 val n = 4 val matrix: Array<IntArray> = Array(m) { IntArray(n) } val graph = Array(numCourses) { mutableListOf<Int>() } val a = IntArray(n) { it } // [0, 1, 2, 3] val visited = Array(m) { BooleanArray(n) } println(myArray.contentToString()) // for debugging. direct printing only gives us the memory address array1.contentEquals(array2) // array1 == array2 won't work as == checks for the reference -
Strings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17var 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. val length = myString.length val sortedString = myString.toCharArray().sorted().joinToString("") // .isLetterOrDigit() // .lowercaseChar() -
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 6 7 8 9 10for (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) for (i in nums.indices) // instead of i in 0 until nums.size repeat(n) { // Easy way of iterating n times the same thing } -
Input:
1val userInput = readLine() -
when:1 2 3 4 5 6 7 8 9when (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
thisto access thethisfrom an outer scope:this@MainActivity. -
How to compile and run in the command line:
1kotlinc main.kt -include-runtime -d main.jar && java -jar main.jar "optional args" -
minOf()maxOf()are similar to Pythonmin()max(). -
Int.MAX_VALUEInt.MIN_VALUE
Collections
-
List,Set,Map,MutableList,MutableSet,MutableMap. -
listOf(),mutableListOf().mList.set(1, "foo"),.shuffle(),.last(),.max(),.maxOrNull(), modify a List directly:myList + 1creates a new list with 1 at the end. -
mapOf(0 to 'a', 1 to 'b', 2 to 'c')for ((key, value) in mMap). (toactually creates aPair<K, V>, pair.first, pair.second).1 2 3 4 5 6 7 8 9 10 11 12 13# TwoSum fun twoSum(nums: IntArray, target: Int): IntArray { val valToIndex = mutableMapOf<Int, Int>() for ((i, num) in nums.withIndex()) { // or this one // nums.forEachIndexed { i, num -> valToIndex[target - num]?.let { return intArrayOf(it, i) } valToIndex[num] = i } return intArrayOf() }1 2 3 4 5 6 7val myMap = mapOf(1 to 2, 3 to 4) for ((k, v) in myMap) for (k in myMap.keys) for (v in myMap.values) myMap.getOrPut(5) { 10 } // the same as computeIfAbsent() or defaultdict myMap[5] = myMap.getOrDefault(5, 0) + 1 -
Add
out(<out T>) to make the generics covariant (use a subtype when a supertype is expected) - achieving polymorphism (like<? extends E>in Java). Addinto 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 8fun <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
into check existence just like in Python. -
Like
enumerate()in Python, we have.withIndex()or we can use.forEachIndex {i, num ->}:1 2 3 4 5for ((index, element) in collection.withIndex()) {...} collection.forEachIndexed{ i, num -> ... } -
Sorting
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15val arr = intArrayOf(3, 5, 1) arr.sort() arr.sortByDescending() arr.sortWith(compareBy({ it % 2 }, { it })) // Primary key and secondary key val sorted = arr.sorted() // sortedBy(), sortedDescending(), sortedByDescending { it } val intervals = arrayOf( intArrayOf(1, 2), intArrayOf(3, 4), intArrayOf(5, 6) ) intervals.sortWith(compareBy({ it[0] }, { it[1] })) val pairs = mutableListOf(1 to 2, 5 to 1, 3 to 3) pairs.sortBy { it[0]} pairs.sortByDescending { it[0] } -
Heap/Priority queue
1 2 3 4 5 6val minHeap = PriorityQueue<Int>() val maxHeap = PriorityQueue<Int>(compareByDescending { it }) minHeap.add(1) maxHeap.add(2) minHeap.peek() minHeap.poll() // removes the element -
Stacks && Queues
1 2 3 4 5 6 7 8val stack = ArrayDeque<Int>() stack.addLast(1) stack.last() stack.removeLast() val queue = ArrayDeque<Int>() queue.addFirst(1) queue.first() queue.removeLast() -
Kotlin list to array
Array<>istoTypedArray()instead oftoArray(). Otherwise it’stoIntArray()forIntArray. -
.zip()and.unzip()for lists in Kotlin (similar to Python).1 2 3 4val list1 = listOf(1, 2, 3) val list2 = listOf('a', 'b', 'c') val zipped: List<Pair<Int, Char>> = list1.zip(list2) // [1 to a, 2 to b, 3 to c] val (nums, chars) = zipped.unzip()
OOP
-
Example class:
1 2 3 4 5 6 7 8 9class 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 5var myProperty: String get() = field set(value) { field = value } -
Classes, variables, and methods are final by default. To enable inheritance and overriding, add
openbefore them. We also need to addoverrideto variables. -
The
init {}blocks are called during initialization. -
If a property is defined using
valin 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. -
abstractfor classes, variables, and methods. These properties must all be overridden later. -
An
interfacelets 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
isto check the type:if (animal is Wolf). And useasfor explicit casting. -
Anyis the superclass of everything. -
Add
datato the start of the class to make it behave like astructin Swift. Then we can use==or.equalsto 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
dataobjects, we have destructuring declaration:1 2 3 4 5val (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:
1constructor(foo: Boolean) : this(0, foo) {} // Calls the primary constructor. -
enumclass:1 2 3 4 5 6 7 8 9 10enum 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
publicby default. We also haveprivate,protected, andinternal(for a module).class Foo(private val bar: Int)makes bar private.class Foo(bar: Int)makes bar just a param instead of a publicly accesible property. -
Operator overloading:
1 2 3 4 5data 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 3w?.let { // executed when w is not null } -
The Elvis operator
?:1w?.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 finallyblock for exception handling. And the samethrow. But we can usetryas an expression, which returns the value to the variable:1 2 3 4 5 6val 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 returnsnullis the casting is not possible:1val 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 5 6val addFive: (Int) -> Int = {x: Int -> x + 5} val result = addFive.invoke(1) // 6 // or val result = addFive(1) val addition: (Int, Int) -> Int = {x: Int, y: Int -> x + y} println(addition(2, 3)) // 5 -
{it + 5}is equivalent to{x -> x + 5}. -
Lambdas can also be passed to functions.
1 2 3 4fun 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 (::foocalls 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 5var 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} -
allandany:1listOf(Person("Alice", 27), Person("Bob", 31)).all {it.age < 28} -
flatMap {}andflatten():1 2 3 4 5 6 7 8val 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 4people.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 17fun 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 -> ... }