Stuff Kotlin does

Have you heard of Kotlin?

It’s an interesting language that tries to prevent programmers from shooting themselves in the foot while trying to maintain the efficiency of the JVM. Here are some interesting and sometimes (pleasantly) surprising things Kotlin does.

Null needs to be explicity defined

if vs when

Both are expressions that can return values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
print("Enter the temperature:")

val input = readLine()
if (input == null) return

val comment = if (input.toInt() > 37) {
println("seems hot...")
"Make an omlette on the pavement"
}
else if (input.toInt() < 0) {
"Brrr...that's cold"
}
else {
"Nice day for a walk"
}
println(comment)
}
1
2
3
4
5
6
7
8
9
10
11
fun main() {
print("Enter the temperature:")
val input = readLine()
val comment = when(input?.toInt()) {
in -40..5 -> "Brrr...that's cold"
in 6..24 -> "Mmmm...that's crisp"
in 25..37 -> "Getting hot in here"
else -> "Make an omlette on the pavement"
}
println(comment)
}

arrays

You can create arrays of mixed types

1
2
3
4
5
6
fun main() {
var collection = arrayOf("Apple", 23.6, 'D', 77)
for (e in collection) {
println(e)
}
}

If we convert to bytecodes and look at the Java equivalent we see it’s just an array of Objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import kotlin.Metadata;

@Metadata(
mv = {1, 1, 13},
bv = {1, 0, 3},
k = 2,
d1 = {"\u0000\b\n\u0000\n\u0002\u0010\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001¨\u0006\u0002"},
d2 = {"main", "", "Test"}
)
public final class AppKt {
public static final void main() {
Object[] collection = new Object[]{"Apple", 23.6D, 'D', 77};
Object[] var3 = collection;
int var4 = collection.length;

for(int var2 = 0; var2 < var4; ++var2) {
Object e = var3[var2];
System.out.println(e);
}

}

// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}

Strings

Strings characters can be accessed with index.

Named Loops

You can break out of specifically labeled loops.

1
2
3
4
5
6
7
8
fun main() {
outer@ for (i in "This is the outer loop") {
for (j in "This is the inner loop") {
if (j in "aeiou") break@outer
println("$i .. $j")
}
}
}

Functions

It’s cool that functions can be defined as expressions

1
fun product(a: Int, b: Int) = a * b

Smart Casts

Safe casts are inserted when needed.

1
2
3
4
5
6
7
val a :Any
a = "45"

if (a !is String) return
println("Len = ${a.length}")
println("this prints too")
println("and so does this")

The lines after the condition are only executed for Strings.

Properties

Classes can have properties which can be marked as read-only(val) or read-write(var). Variables be initialized to be used. If it cannot be initialized then qualify it with lateinit.

Constructors

There are primary and secondary constructors.

Primary

1
2
3
4
5
6
class Dog(val name :String)

fun main() {
var d = Dog("Spike")
println("My name is ${d.name}")
}

Secondary

1
2
3
4
5
6
7
8
9
10
11
class Dog {
val name: String
constructor(name: String) {
this.name = name
}
}

fun main() {
var d = Dog("Spike")
println("My name is ${d.name}")
}

Data classes

Inheritance

Modifiers are explicit at the function level. By default functions are marked as closed and need to be qualified with open to be able to override it.

1
2
3
4
5
6
7
open class Bird{
open fun squawk() {}
fun peck() {}
}
class Derived() : Bird() {
override fun squawk() {}
}

Classes and Properties

Getters and Setters are declared as follows:

1
2
3
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]

Abstract classes

Abstract classes allow us to define specific traits similar to interfaces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Plane(val name: String) {
abstract fun fly()
}

fun main() {
val p = F120("Zippy")
println(p)
}

class F120(myname: String): Plane(myname) {
override fun fly() {
println("zooooom")
}
override fun toString(): String {
return "name --> $name"
}
}

Functional Programming [TODO]

One of the paradigms that can be used.

1
2


Coroutines

Basic example

1
2
3
4
5
6
7
8
9
10
import kotlinx.coroutines.*

fun main() {
GlobalScope.launch { // launch new coroutine in background and continue
delay(3333L) // non-blocking delay for 1 second (default time unit is ms)
println("${Thread.currentThread()} World!") // print after delay
}
println("${Thread.currentThread()} Hello,") // main thread continues while coroutine is delayed
Thread.sleep(5000L) // block main thread for 2 seconds to keep JVM alive
}

The definitive guide to Kotlin Coroutines

Example: Reversing Lists

1
2
3
4
5
6
7
8
9
10
11
fun main() {
println(reverse(listOf(1,2,3,4,5,6)))
}

fun reverse(list: List<Int>): List<Int> {
val result = arrayListOf<Int>()
for (i in 0..list.size - 1) {
result.add(list[list.size - 1 - i])
}
return result
}

A little more intuitive solution

1
2
3
4
5
6
7
8
9
10
11
fun main() {
println(reverse(listOf(1,2,3,4,5,6)))
}

fun reverse(list: List<Int>): List<Int> {
val result = arrayListOf<Int>()
for (i in list.size - 1 downTo 0) {
result.add(list[i])
}
return result
}

Example: A game of Hangman

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package codealong

data class Result(val failed: Boolean, val numTries: Int)

fun main(args: Array<String>) {
print("What word do you want to guess? ")
val word = readLine()
if (word == null) {
println("No word provided...exiting")
return
}

for (i in 1..100) println()

val letters = word.toLowerCase().toCharArray().toHashSet()
val correctGuesses = mutableSetOf<Char>()
var totalTries = 0

while (letters != correctGuesses) {
print("Enter letter: ")
val c = readLine()
if (c == null) {
println("Bad input")
return
}
val (failed , numTries) = guessWord(c.first(), correctGuesses, totalTries, word)
totalTries = numTries
if (!failed) break
}
println("Number of wrong guesses = $totalTries");
}

fun guessWord(char: Char, correctGuesses: MutableSet<Char>, fails: Int, word: String): Result {
var failed = false
var count = fails
//if (correctGuesses.contains(char)) return Result(false, fails)
for(c in word) {
if (c == char) {
print("$c ")
correctGuesses.add(c)
} else if (correctGuesses.contains(c)) {
print("$c ")
} else {
print("_ ")
failed = true
}
}
if (failed) count++
return Result(failed, count)
}

Example: Get Max IP address count from file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package collections

import java.io.File

fun main(args: Array<String>) {
var file = File("input.txt")
val addrCount = mutableMapOf<String, Int>()
var maxPair: Pair<String, Int> = Pair("", 0)
file.forEachLine {
addrCount.put(it, addrCount.getOrDefault(it, 0) + 1)
maxPair = if (maxPair.second < addrCount.get(it)!!) Pair(it, addrCount[it]!!) else maxPair
}
println(maxPair)
}

Example: Capitalize Words in a Sentence

Implementation

1
2
3
4
5
6
package capitalizeSentence

fun capitalizeLetter(sentence: String) : String {
val s = sentence.split(" ").joinToString(separator = " ") { it.capitalize()}
return s
}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package capitalizeSentence

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test

internal class MainKtTest {

@Test
fun capitalizeLetterBasic() {
assertEquals(capitalizeSentence.capitalizeLetter("hello world"), "Hello World")
}

@org.junit.jupiter.api.Test
fun checkWithMoreSpaces() {
assertEquals(capitalizeSentence.capitalizeLetter("hello world"), "Hello World")
}

@org.junit.jupiter.api.Test
fun checkWithComma() {
assertEquals(capitalizeSentence.capitalizeLetter("hello , world"), "Hello , World")
}

}

Example: Range Within Range

Given two ranges implement a function which checks if range1 contains range2.

Implementation

1
2
3
4
5
6
7
8
package Ranges

/**
* Checks if range1 contains range2
*/
fun checkSubrange(range1: IntRange, range2: IntRange): Boolean {
return ((range2.first in range1) and (range2.last in range1))
}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Ranges

import org.junit.jupiter.api.Test

import org.junit.jupiter.api.Assertions.*

internal class CheckSubrangeKtTest {

@Test
fun `range 5-7 contains 5-7`() {
assertTrue(checkSubrange(5..7, 5..7))
}

@Test
fun `range 6-7 does not contain 5-7`() {
assertFalse(checkSubrange(6..7, 5..7))
}

@Test
fun `range 5-8 does not contain 3-5`() {
assertFalse(checkSubrange(5..8, 3..5))
}
}

Example: Add number up to N

Given positive integer n implement a function which calculates sum of all numbers from 1 up to (and including) number n.

Implementations

1
2
3
4
5
6
7
8
9
10
import java.lang.IllegalArgumentException

fun addUpTo(p: Int): Int {
if (p < 0) throw IllegalArgumentException("Cannot solve for negatives!")
var amount = 0
for (e in 0..p) {
amount += e
}
return amount
}

More idiomatic solutions

1
2
3
4
5
6
7
8
9
fun addUpTo2(p: Int): Int {
if (p < 0) throw IllegalArgumentException("Cannot solve for negatives!")
return (1..p).sum()
}

fun addUpTo3(p: Int): Int {
if (p < 0) throw IllegalArgumentException("Cannot solve for negatives!")
return (0..p).fold(0) {accumulated, current -> accumulated + current}
}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
internal class MainKtTest {

@Test
fun `fail with negative`() {
assertFailsWith<IllegalArgumentException> { addUpTo(-4) }
}

@Test
fun `add upto 3`() {
assertEquals(addUpTo(3), 6)
}
}

internal class MainKtTest2 {

@Test
fun `fail with negative`() {
assertFailsWith<IllegalArgumentException> { addUpTo2(-4) }
}

@Test
fun `add upto 3`() {
assertEquals(addUpTo2(3), 6)
}

@Test
fun `check 0`() {
assertEquals(addUpTo2(0), 0)
}
}

internal class MainKtTest3 {

@Test
fun `fail with negative`() {
assertFailsWith<IllegalArgumentException> { addUpTo3(-4) }
}

@Test
fun `add upto 3`() {
assertEquals(addUpTo3(3), 6)
}

@Test
fun `check 0`() {
assertEquals(addUpTo3(0), 0)
}
}

Resources