Elements of Kotlin - by Mark L. Murphy
…
…
HelloWorld().speak()HelloWorld() returns an instance of a HelloWorld object. There is no need for a keyword, the way Java, Ruby, or JavaScript uses new.…
Kotlin’s basic types are modeled somewhat after Java, as Kotlin’s original target environment was the JVM.
But there are also some differences.
int → IntegerInt or DoubleLong, use L suffix12345LFloat, use F suffix1.2345f1_2341_2_3_4 is also perfectly valid.0x prefix 0xFFA4C6390b prefix 0b10110100+ for addition- for subtraction* for multiplication/ for division% for the remainder after division (“modulo”)true or false&& → AND|| → OR! → NOT“ - double-quotes\n, \t etc.”“” - triple-quotestrimMargin() function on String to eliminate unwanted indentation.“one” + “two”“The value of count is $count”“The value of count is ${count+2}”println('A')== → means “content equality”equals() in Java)=== → means “instance equality” == in Java)===) is determined a bit by Kotlin but mostly by the Kotlin environment. 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
var myVar = 5myVar::class will be kotlin.Int (in Kotlin/JVM environment) var count: Long = 5valval will result in an exceptionval Over varvar only if it is necessary 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}")
+, -, /, % etc+=, -=, *=, /=, %=count += 2-count (negation)!count (true will be false and vice versa)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()
null to a variable whose type ends in ?, and we cannot assign null to a variable whose type does not end in ?.var something : Boolean? = null println("something was: $something") something = true println("something is now: $something")
fun lessTrivial(left: Int, right: Int) { println(left + right) }
At first the parameter name and then colon and the parameter type.
fun simpleReturn(left: Int, right: Int) : Int { return left + right }
Int in this example).return keyword returns nothing (void in Java), but technically seen they return Unit.fun actLocally(left: Int, right: Int) : Int { val result = left + right return result }
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.
Major collection types are like in the other popular modern languages:
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.
val things = arrayOf(“foo”, “bar”, “goo”)BooleanArray, ByteArray, CharArray, DoubleArray, FloatArray,IntArray, LongArray, ShortArrayintArrayOf():val things = intArrayOf(1, 3, 3, 7)listOf(“foo”, “bar”, “goo”)val things = setOf(1, 3, 3, 7)1,3,7)List, Set is an interface. The concrete implementation that you will get from setOf() is a LinkedHashSetsetOf() creates an immutable SetmapOf() utility function. This takes a comma-delimited list of key-value pairs, where the key and value are separated by the to keywordmapOf(“key” to “value”, “other-key” to “other-value”)mapOf(“odd” to listOf(1, 3, 5, 7, 9), “even” to listOf(2, 4, 6, 8))thingsArray[0]size()isEmpty(), for a quick Boolean value based on whether the collection is emptycontains(), which will return true if the collection contains the requested item (for maps, this checks the keys — use containsValue() to check the values)first(), to get the first elementlast(), to get the last elementfirstOrNull(), to get the first element or null if the collection is emptyArray offers toList() toSet()List offers toSet() toTypedArray()Set offers toList()toTypedArray()
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.
val lambda = { println(“Hello, world!”) }invoke():val lambda = { println("Hello, world!") } lambda.invoke()
val squarifier = { x: Int -> x * x } println(squarifier.invoke(3))
forEach() and for)forEach()val things = listOf("foo", "bar", "goo") things.forEach { println(it) }
forEach() takes a lambda expression as a parameter. It may not look like a parameter, again due to the lack of parentheses. The same code:val things = listOf("foo", "bar", "goo") things.forEach( { println(it) } )
forEach() — just as a function can declare a parameter as being an Int or a String, it can declare a parameter as being a lambda expression that itself takes a certain parameter.forval things = listOf("foo", "bar", "goo") for (thing in things) { println(thing) }
forEach() is somewhat more popular as for, as it can be used in chained expressions.filter()filter() applies a filtering lambda expression to a collection, returning a new collection that contains the subset of items from the original collection. If the lambda expression returns true for an item, that item is included in the result collection, otherwise it is not.val things = listOf("foo", "bar", "goo") things.filter { it[1]=='o' }.forEach { println(it) }
map()filter() passes along a strict subset of the items in the collection, but the items themselves are unchanged. map(), by contrast, passes along each item from the collection, transformed by a lambda expression that you supply:val things = listOf("foo", "bar", "goo") things .map { it.toUpperCase() } .forEach { println(it) }
filter() takes a collection and returns a subset of its items, map() takes a collection and returns a transformed representation of its items.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) } }
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.
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.
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.
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") }
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.
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.
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.
break says “abandon the loop”continue says “skip the rest of this pass through the loop, and move along to the next pass through the loop”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.
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) }
The syntax to declare package is Java-like:
package bar.goo class Foo val foo = Foo()
class Foo { fun something() { println("Hello, world!") } } fun main() { val foo = Foo() foo.something() }
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}") }
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") } }
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") } } // ...
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:
init block. They are executed in the order that they appear in the class.init block can only reference properties that are declared before it in the class, not ones declared after it. As a result, an init block often appears after the list of properties, so the init block can reference any of the properties.var or val keyword. They just have the parameter name and type. They are still constructor parameters, but they are not properties. You cannot refer to them from the functions of your class. You can refer to them from init blocks, though.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.
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.
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.
open.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") } }
open is transitive. Once a function is marked as open, it is open for all subclasses down the hierarchy, regardless of whether intervening classes override the function or not.final
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") } }
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") } }
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!") } }
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() } //...
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.
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 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.
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.
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.
publicprivateprivate limits access to instances of its class, and nothing else.private is not just available for aspects of classes. Classes themselves can be private. Properties and functions defined outside of classes can also be marked as private. In these cases, the determination of what can and cannot access the private items is based on the file:private top-level things are visible to other things in the same Kotlin source fileprivate top-level things are inaccessible to other things in other Kotlin filesprotectedprotected visibility modifier works a lot like private, but it also allows subclasses to access the property or function.protected is defined as “accessible by this class and its subclasses”, so it only makes sense in the context of classespackageinternalinternal is visible to code in the same module but is not visible to code in other modules.internal will be important. internal can be useful. internal is pointless.val, etc. — will exist for the life of the process that is executing the Kotlin code. var — have references that will live forever, but the objects that those references reference might come and go as the value of that reference changes.