Table of Contents

Elements of Kotlin

Elements of Kotlin - by Mark L. Murphy

Outline

Introducing Kotlin

Why?

...And Why Not?

What You Need to Know

Look At All the Kotlins!

Getting Kotlin, Normally

Introducing the Klassbook

How This Book is Structured

A Few “Hello, World!” Examples

…

So, What Does This Do?

…

Using the Klassbook

…

Running These Snippets in an IDE

Basic Types and Expressions

Kotlin’s basic types are modeled somewhat after Java, as Kotlin’s original target environment was the JVM.

But there are also some differences.

Basic Types and “Objectness”

Numbers

Boolean

Strings

Characters

A Quick Note About Equality

val value = "something"
println(value.toUpperCase().toLowerCase() == value)   // prints true
println(value.toUpperCase().toLowerCase() === value)  // prints false in Kotlin/JVM and true in Kotlin/JS

Variables… Whether They Vary or Not

Declaring Variables

Declaring Read-Only Variables

String Interpolation

  val count = 5
  println("The value of count is $count")

Arbitrary Kotlin expressions can be used with ${} syntax:

val count = 3
println("The value of count is not ${count+2}")

More Operators

No Automatic Number Conversions

This will not work in Kotlin:

val thisIsInt = 5
val thisIsLong : Long = thisIsInt

(but it works in Java)

In Kotlin the code should be:

val thisIsInt = 5
val thisIsLong : Long = thisIsInt.toLong()

Hey, What About ?

var something : Boolean? = null
println("something was: $something")
 
something = true
println("something is now: $something")

Functions

Functions with Parameters

fun lessTrivial(left: Int, right: Int) {
  println(left + right)
}

At first the parameter name and then colon and the parameter type.

Functions with Return Types

fun simpleReturn(left: Int, right: Int) : Int {
  return left + right
}

Local Variables

fun actLocally(left: Int, right: Int) : Int {
  val result = left + right
 
  return result
}

Fancier Functions

Expression bodies:

fun expressionBody(left: Int, right: Int) = left + right

Expression Bodies and Types:

fun expressionBody(left: Int, right: Int):Number = left + right

Number is a super type of Int and sometimes it could be necessary to use it instead of Int.

Default parameter values:

fun main() {
  println(increment(1))
}
 
fun increment(base: Int, amount: Int = 1) = base + amount

Named parameters:

fun main() {
  println(increment(base = 1))
  println(increment(amount = 10, base = 1))
  println(increment(1, amount = 10))
}
 
fun increment(base: Int, amount: Int = 1) = base + amount

It is a simple example and it does not make much sense. But sometimes it is really helpful. For example:

fun main() {
  val cookieHeader = iCanHazCookie(name = "foo", value = "bar", path = "/")
 
  // or even:
  cookieHeader = iCanHazCookie("foo", "bar", path = "/")
}
 
 
 
fun iCanHazCookie(name: String,
                  value: String,
                  maxAge: Long? = null,
                  expires: LocalDateTime? = null,
                  domain: String? = null,
                  path: String? = null,
                  secure: Boolean = false,
                  httpOnly: Boolean = false) : String {
  // code to generate an HTTP Set-cookie header goes here
  // ...
}

Traditionally, named parameters had to appear after all positional parameters. Kotlin 1.4 relaxed that restriction somewhat. You can use names for any parameters, but up through the last positional parameter, the positions (not the names) are what matter.

Collections and Lambdas

Major collection types are like in the other popular modern languages:

Major Collection Types and Creation Functions

Kotlin has the aforementioned roster of collection types, and they form the most common collection types that you will use.

Kotlin/JVM also has access to other standard Java collection types, like LinkedHashMap, which we will discuss later in this chapter. However, usually you will use those only if there is no native Kotlin equivalent.

Basic Usage

Immutability and Collections

Array objects that we create with arrayOf() are mutable. Not only can we use [] syntax to retrieve a value based on its index, but we can use []= syntax to replace a value based on its index.

val thingsArray = arrayOf("foo", "bar", "goo")
 
thingsArray[1] = "something completely different"

However, this fails if you try it with a List created using listOf():

val thingsList = listOf("foo", "bar", "goo")
 
thingsList[1] = "something completely different"

A List is immutable: you cannot replace its members with other objects.

If you need a list where you can replace its members, you can use mutableListOf():

val thingsList = mutableListOf("foo", "bar", "goo")
 
thingsList[1] = "something completely different"

The same distinction holds true for Map. A Map is immutable, but a MutableMap created via mutableMapOf() can be modified.

Introducing Lambda Expressions

val lambda = { println("Hello, world!") }
 
lambda.invoke()

Common Collection Operations

Varargs

fun main() {
  reciprocate(1, 2, 3, 5, 7)
}
 
fun reciprocate(vararg values: Int) {
  values
    .map { 1.0 / it }
    .forEach { println(it) }
}

In addition, Kotlin offers special support for an existing Array. If you already have an Array with the values that you want to supply, but the function that you are calling uses vararg (instead of an Array parameter), you can use the “spread operator”

fun main() {
  val things = arrayOf("foo", "bar", "goo")
 
  capped(*things).forEach { println(it) }
}
 
fun capped(vararg strings: String) = strings.map { it.toUpperCase() }

Things get a bit strange with types that map to primitives, though. vararg expects the dedicated array types to be used with the spread operator. So, in the following example, the reciprocate() function takes a vararg of Int. For the spread operator to work, we need to use an IntArray, not a regular Array:

fun main() {
  val primes = intArrayOf(1, 2, 3, 5, 7)
 
  reciprocate(*primes)
}
 
fun reciprocate(vararg values: Int) {
  values
    .map { 1.0 / it }
    .forEach { println(it) }
}

Other JVM Collections

If you are using Kotlin/JVM — for example, if you are using Kotlin for ordinary Android app development — you are welcome to use other JVM collection types, such as LinkedList. On the whole, you should stick with Kotlin collection types where possible, as the Kotlin types are more “natural” for use in Kotlin. But sometimes you will find a Java class that fits your scenario better, such as using a LinkedHashMap to implement an LRU-style cache.

get() and [] Syntax

In reality, [] syntax is available for anything that offers a one-parameter get() function. When Kotlin encounters [] syntax, it simply replaces that with the corresponding get() call.

If, When, and While

If

val i = 3
 
if (i > 10) {
  println("something")
}
else {
  println("something else")
}

else is optional:

val i = 3
 
if (i > 10) {
  println("something")
}
 
println("the if is done")

For single-line conditional work, you can skip the braces:

val i = 3
 
if (i>10) println("something") else println("something else")

else if is also possible:

val i = 3
 
if (i > 10) {
  println("something")
}
else if (i > 2) {
  println("something else")
}
else {
  println("and now for something completely different")
}

However, as you will see in the next section, the convention in Kotlin development is to use when instead of if for such scenarios.

When

Basic syntax:

val thingy = "foo"
 
when (thingy) {
  "foo" -> println("something")
  "bar" -> println("something else")
  else -> println("and now for something completely different")
}

However, you cannot mix types, unless == happens to work to compare them.

It is possible to skip the parameter to the when. In that case, each branch has its own Boolean expression, and the first that evaluates to true is used:

val i = 3
 
when {
  i > 10 -> println("something")
  i > 2 -> println("something else")
  else -> println("and now for something completely different")
}

Also we can consolidate multiple branches:

val thingy = "foo"
 
when (thingy) {
  "foo", "goo" -> println("something")
  "bar", "baz", "frobozz" -> println("something else")
  else -> println("and now for something completely different")
}

For numbers, you can use .. syntax and in to set up a “range” and test against it:

val i = 3
 
when (i) {
  in 3..10 -> println("something")
  in 1..2 -> println("something else")
  else -> println("and now for something completely different")
}

However, these simplifications only work when you supply a value to when, as opposed to leaving when empty and using boolean expressions for the branches.

The value for comparison does not need to be a constant — any expression will also work:

val thingy = "bar"
val otherThingy = "BAR"
 
when (thingy) {
  "foo", "goo" -> println("something")
  otherThingy.toLowerCase() -> println("something else")
  else -> println("and now for something completely different")
}

While

var i = 0
 
while (i < 10) {
  i++
  println(i)
}

However, while is not as common in Kotlin as it is in other languages. More often we use operations on collections (e.g., forEach()) rather than while.

If/When As Expressions

Kotlin lacks ternary support, but more than makes up for it by allowing if to serve in the same role:

val i = 3
 
val j = if(i < 2) 2 else i

Also possible with braces:

val bar = 5
val foo = if (bar > 10) {
  10
}
else {
  bar
}
 
println(foo)

The value of the last statement of the block is what that block evaluates to. In this case, each of those blocks is a single statement, but if we had several statements in a block, while they are all executed, the value from the last statement is used for the result (assuming that block is used, based on the conditional expression).

when can also be used as an expression:

val i = 3
 
val message = when (i) {
  in 3..10 -> "something"
  in 1..2 -> "something else"
  else -> "and now for something completely different"
}
 
println(message)

In the situations, when if or when is used as expression, they must be exhaustive (the expression should work for all cases!). If you try a non-exhaustive if or when for an expression, such as:

val i = 3
 
val message = when (i) {
  in 3..10 -> "something"
  in 1..2 -> 5
}
 
println(message)

…you will get a compiler error. Because it is not clear what should be done in else case.

Bustin’ Out

You can always use return to exit the entire function that you are in. Beyond that, though, there are two loop control statements that you can use: break and continue. These work much like their counterparts in Java and other programming languages.

Basic Classes

Basic Classes

A class does not have to contain any of that — this is a valid Kotlin class definition:

class Foo {
 
}

This is also valid:

class Foo

Having a class with no body seems odd. However, in Kotlin, you will occasionally see that sort of thing, particularly with data classes and sealed classes. However, most ordinary classes will have properties and functions.

Creating Instances of Classes

To create an instance of a class it is not necessary to use new() like in Java. Simply call the class like function:

class Foo
 
fun main() {
  val foo = Foo()
 
  println(foo)
}

Packages

The syntax to declare package is Java-like:

package bar.goo
 
class Foo
 
val foo = Foo()

Common Contents

Functions

class Foo {
  fun something() {
    println("Hello, world!")
  }
}
 
fun main() {
  val foo = Foo()
 
  foo.something()
}

Properties

Classes can also have properties:

class Foo {
  var count = 0
 
  fun something() {
    count += 1
    println("something() was called $count times")
  }
}
 
fun main() {
  val foo = Foo()
 
  foo.something()
  foo.something()
  foo.something()
 
  println("the final count was ${foo.count}")
}

this

As with Java, Kotlin uses this as a pseudo-property to refer to the instance of the object in whose function you happen to be running.

class Foo {
  var count = 0
 
  fun something() {
    this.count += 1
    println("something() was called $count times")
  }
}

Constructors

The way you will normally see Kotlin classes have a constructor is by making the actual class name itself take the constructor parameters:

class Foo(var count: Int) {
  fun something() {
    count += 1
    println("something() was called $count times")
  }
}
 
fun main() {
  val foo = Foo(0)
 
  foo.something()
  foo.something()
  foo.something()
 
  println("the final count was ${foo.count}")
}

The constructor syntax shown above is really a shorthand for using the constructor keyword:

class Foo constructor(var count: Int) {
  fun something() {
    count += 1
    println("something() was called $count times")
  }
}
// ...

Init Blocks

Syntax-wise, an init block resembles a Java static block: a set of braces-wrapped statements preceded by a keyword. In Java, the static block is executed once for the class and is used to initialize complex static fields. In Kotlin, an init block is executed once for each instance and serves as a baseline constructor body:

class Foo(val sillyCount: String) {
  var count = 0
 
  init {
    count = sillyCount.toInt()
  }
 
  fun something() {
    count += 1
    println("something() was not called $count times")
  }
}
 
 
fun main() {
  val foo = Foo("7")
 
  foo.something()
  foo.something()
  foo.something()
 
  println("the final count was ${foo.count}")
}

init blocks have a couple of other distinctions, particularly when compared to Java/Ruby constructors:

Having Multiple Constructors

The primary constructor appears in the class declaration itself, as we have seen. Other constructors, if they exist, are called “secondary constructors”, and they somewhat resemble ordinary functions:

class Foo(var count: Int) {
 
  constructor(sillyCount: String) : this(sillyCount.toInt()) {
    // could have code here if needed
  }
 
  fun something() {
    count += 1
    println("something() was not called $count times")
  }
}
 
 
fun main() {
  val foo = Foo("7")
 
  foo.something()
  foo.something()
  foo.something()
 
  println("the final count was ${foo.count}")
}

A primary constructor has no function body — any such code goes into an init block. Secondary constructors, though, can have bodies, the way that functions do, but only if needed.

Secondary constructors must arrange to call the primary constructor. This is handled by having a this() call after the constructor parameter list, separated from that list via a colon. The this() call needs to match the parameter list of the primary constructor — in the example shown above, the secondary constructor needs to provide a Int to the primary constructor.

Default Parameter Values

Kotlin supports default parameter values in constructors:

 
class View(
  context: Context,
  attrs: AttributeSet? = null,
  defStyleAttr: Int = 0,
  defStyleRes: Int = 0
) {
  // two tons of code
}

Here, the latter three parameters all have default values defined, giving us the ability to call the constructor with one parameter, all four parameters, or combinations in between.

Inheritance

Kotlin uses colons to indicate inheritance:

class Foo(var count: Int) : Any() {
  fun something() {
    count += 1
    println("something() was called $count times")
  }
}

(the Java counterpart is the keyword extends)

Chaining the super class contructor:

open class Base(val tag: String)
 
class Foo(var count: Int) : Base("Foo Example") {
  fun something() {
    count += 1
    println("$tag: something() was called $count times")
  }
}

In Java, by default, any class can be extended by a subclass. To prevent a class from being extended, you have to mark it as final.

Kotlin goes the opposite route. Kotlin classes cannot be extended by default. You have to mark classes that can be extended using open. Failure to do that results in an error.

Overriding Functions

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}
 
class Foo(var count: Int) : Base("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"
 
  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

Overriding Properties

It is also possible for a class to declare a property as open, allowing subclasses to override it.

open class Base(open val tag: String) {
  open fun gimmeTheTag() = tag
}
 
open class Intermezzo(override val tag: String) : Base(tag)
 
class Foo(var count: Int) : Intermezzo("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"
 
  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

Chaining to Superclass Functions

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}
 
class Foo(var count: Int) : Base("Foo Example") {
  override fun gimmeTheTag() = super.gimmeTheTag() + "-Extended"
 
  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

Inheritance Tests

Kotlin has syntax similar to Java's instanceof , with a shorter keyword: is. So, we can do this:

open class Animal
 
class Frog : Animal()
 
class Axolotl : Animal()
 
fun main() {
  val critter: Animal = Frog()
 
  if (critter is Frog) println("Ribbit!") else println("Ummm... whatever noise an axolotl makes!")
}

In Kotlin, we can use is not only as part of an expression, but we can use it as a test for a when clause. We can rewrite the previous example using a when:

open class Animal
 
class Frog : Animal()
 
class Axolotl : Animal()
 
fun main() {
  val critter: Animal = Frog()
 
  when (critter) {
    is Frog -> println("Ribbit!")
    else -> println("Ummm... whatever noise an axolotl makes!")
  }
}

Casting

In Kotlin, casting is performed by using the as keyword:

val critter: Animal = Frog()
val kermit = critter as Frog

Inside of if and when blocks, if we know that a variable is really of some subtype, the compiler lets us skip the casts and allows us to refer to the variable as the subtype, even though it was declared as a supertype.

open class Animal
 
class Frog : Animal() {
  fun hop() = println("Hop!")
}
 
class Axolotl : Animal() {
  fun swim() = println("Swish!")
}
 
fun main() {
  val critter: Animal = Frog()
 
  when (critter) {
    is Frog -> critter.hop()
    is Axolotl -> critter.swim()
  }
}

In a Java-style world, this would not compile. critter is an Animal, and Animal has neither hop() nor swim(). However, Kotlin realizes that the is Frog code will only be executed if critter is a Frog, so it allows us to call hop() on critter, as if critter were declared as a Frog instead of as an Animal. Similarly, we can call swim() on critter if it is Axolotl.

But this works only if compiler is sure. This code will not compile, because it is unclear in else whether critter is really Axolotl:

//...
when (critter) {
  is Frog -> critter.hop()
  else -> critter.swim()
}
//...

Comments and Documentation

Basic Comment Syntax

All is like in Java. With one exception:

The biggest — and perhaps only — difference between Kotlin comments and what you might do in Java is that multi-line comments can be nested in Kotlin.

Introducing KDoc

Kotlin’s equivalent to JavaDoc is KDoc.

Where KDoc breaks from JavaDoc is in terms of text formatting. KDoc uses Markdown.

What Markdown lacks is a way to link to things inside the same document. KDoc extends Markdown linking syntax, allowing you to reference properties, functions, and similar named things by using simple bracket syntax:

/**
 * This is the summary of your Kotlin class. It should explain, in a single
 * paragraph, the role of this class in your project.
 *
 * This class has a single function: [something].
 *
 * You can also link to other classes, such as [kotlin.String], using the
 * fully-qualified class name.
 *
 * And, of course, you can [link to external content](https://commonsware.com).
 */

As with JavaDoc, KDoc supports “block tags” for documenting sub-elements of class, function, etc. For example, you can use @param to document a function parameter, @return to document a function return value, and so forth.

Just as Java has a javadoc program to generate JavaDocs, Kotlin has a similar tool: Dokka.

Properties

Initialization

Properties need to be initialized at some point.

Initialization when declared (count):

class Foo(sillyCount: String) {
  var count = sillyCount.toInt()
 
  fun something() {
    count += 1
    println("something() was not called $count times")
  }
}

In an init block:

class Foo(sillyCount: String) {
  var count: Int
 
  init {
    count = sillyCount.toInt()
  }
  // ...
}

Initialization “sometime later”:

class Foo {
  var count = 0
  lateinit var label: String
 
  fun defineTag(tag: String) {
    label = tag
  }
 
  fun something() {
    count += 1
    println("$label: something() was called $count times")
  }
}

But in this case the function something() must be called after defineTag(). Where possible, you should try to avoid lateinit, as it is easy to make mistakes and try using the lateinit property before it is initialized.

Also note that lateinit cannot be used for types based on primitives, so you cannot have a lateinit Int, Boolean, etc.

Yet another possibility is to use by lazy:

class Foo(val rawLabel: String) {
  var count = 0
  val label: String by lazy { println("initializing via lazy!"); rawLabel.toUpperCase() }
 
  fun something() {
    count += 1
    println("$label: something() was called $count times")
  }
}

Here, label is initialized lazily. Whenever we first attempt to use label, the lambda expression will be executed, and that value is what we see as the value of label.

One more way to initialize property is to use a function like property. In Kotlin it is possible to call function without parethesis (e.g. forEach). Such functions are named “fake properties”. They will be discussed later in this book.

Constants

Kotlin has a const keyword, which you can use to declare certain val properties as constants. This is somewhat reminiscent of final in Java. val is already a constant but there are some cases for using const in Kotlin. These cases will be discussed later.

Getting Down in the Weeds

A Kotlin property really represents the combination of these three elements:

It is something like JavaBeans, but without the need to implement standard getter and setter.

Some advanced Kotlin syntax — such as the “fake properties” alluded to earlier — take advantage of this, overriding the default getter and/or setter to do something else. But in the most cases the standard is enough.

Visibility and Scope

Visibility

Scope

Abstract Classes and Interfaces

The Objective: Contracts

Abstract Classes

Interfaces

Which Do You Use?

Data Class

The object Keyword

Nested Objects and Classes

Enums and Sealed Classes

Generics

Exceptions

Annotations

Nullability

Scope Functions

Functional Programming

Extension Functions

Java Interoperability

Changes in Newer Kotlin Versions

Custom Accessors

Extension Properties

Escaping Keywords

Escaped Method Names

Property Delegates

Class Delegation

Constants

Abstract Properties

Covariance in Generics

Contravariance in Generics

Anonymous Functions

Local Functions

Local Types

Inline Functions

Inline Classes

Reified Type Parameters

noinline and crossinline

Receivers in Function Types

Renamed Imports

Operator Overloading

Infix Functions

Destructuring Declarations

Labeled Returns

Nothing

Types of Keywords