Kotlin was inspired by many modern programming languages like C#, Groovy, Scala and also Java. Even more, Kotlin can be seen as an extension to the Java language, making it better by adding functionality to existing standard classes (e.g. String
, List
) and of course by providing great features, which are in large part enabled by applying compiler-supported techniques. As in Java, Kotlin programs are entered via a main
method, such as the following:
package de.swirtz.dzone.refcard.examples
fun main(args: Array<String>): Unit {
val inserted = "Kotlin"
println("Let's get started with $inserted")
}
What we can see in this snippet is:
Control Flow: Conditions
In Kotlin you can make use of if
, when
, for
and while
for controlling the behavior of your code. Let’s look at conditions first.
If-Statement
val min: Int
if (x < y) {
min = x
} else {
min = y
}
It’s important to know, that many statements in Kotlin can also be used as expressions, which for instance makes a ternary operator obsolete and apparently shortens the code in most cases:
val min = if (x < y) x else y
When-Statement
A when
statement is very similar to switch
operators and could, in theory, easily replace if-statements as they are much more powerful.
val y = when (x) {
0 -> "is zero"
1 -> "is one"
2, 3 -> "two or three"
is Int -> "is Int"
is Double -> "is Double"
in 0..100 -> "between 0 and 100"
else -> "else block"
}
In a when
statement, which can also be used as an expression, all branches are tried to match the input until one condition is satisfied. If no branch matches, the else
is executed. As shown in the snippet, when
branch conditions can be values, types, ranges and more.
Control Flow: Loops
For-Loop
In Kotlin, there’s no conventional for-loop, as you know it from C or Java. Instead, foreach loops are the default.
for (c in "charSequence") {
//
}
In many cases, looping with an index is necessary, which can easily be achieved with the indices
property that is defined for arrays, lists and also CharSequence
s for example.
for (i in "charSequence".indices) {
println("charSequence"[i])
}
Another way of iterating with indices is possible by using withIndix()
.
for ((i,c) in "charSequence".withIndex()) {
println("$i: $c")
}
Last but not least, Kotlin has ranges, which can also be utilized for indexed iterations as the following shows:
(0 .. "charSequence".length-1).forEach {
print("charSequence"[it])
}
The range in this example is expressed with the common ..
syntax. To create a range which does not include the end element (s.length
), the until
function is used: (0 until s.length)
.
While-Loop
Constructs with while
or do-while
loops are straight-forward, all works as known from other common languages.
Basic Types
In Kotlin everything looks like an object to the user, even primitive types. This means, member functions can be called on every type, although some will be represented as JVM primitives at runtime.
Numbers
The default number types are: Double
, Float
, Long
, Int
, Short
, Byte
* Underscores can be used to make large numbers more readable: val million = 1_000_000
* Number types offer conversion methods like toByte(): Byte
, toInt(): Int
, toLong(): Long
* Characters are no number type in Kotlin
Chars
A Char
represents characters and cannot be treated as a number. They are declared within single quotes, e.g. '42'
An explicit conversion from a Char
to an Int
can be accomplished with the toInt()
method
Booleans
Booleans
can have the two common values true
and false
* They can be operated on with: ||
, &&
and !
Strings
Strings are immutable sequences of characters. * They offer an index operator []
for accessing characters at specified positions * A string literal in Kotlin looks like "Hello World"
or """Hello World with "another String" in it"""
* The latter is called raw string that can contain any character without needing to escape special symbols * String
s in Kotlin may contain template expressions
Arrays
An array is represented by the class Array
, which offers very useful methods to the client. * Values can be obtained via get(index)
or [index]
* Values can be set via set(index, value)
or [index]=value
* Arrays are invariant, i.e. an Array<String>
cannot be assigned to a variable of type Array<Any>
* Special types for arrays of primitive types exist as IntArray
or ShortArray
for instance. Using those will reduce the boxing overhead.
Classes
A simple class can be declared like in this snippet:
class Person constructor(name: String) {}
The primary constructor is part of the class header, secondary constructors can be added in the class body. In the shown case, the constructor
keyword could also be omitted, since it’s only mandatory if you want to add annotations or visibility modifiers (default: public). Constructor parameters such as name
can be used during the initialization of an object of this class. For this purpose, an init
block would be necessary, because primary constructors can’t contain code directly. Constructor arguments can also be used in property initializers that are declared in the class body, as shown here.
class Person(name: String, age: Int) {
init {
println("new Person $name will be born.")
}
val ageProp = age
}
As mentioned, Kotlin classes can contain properties, which are accessed by simply calling obj.propertyName
to get a property’s value and obj.propertyName = "newValue"
to modify the value of a mutable (var
) property. Declaring properties for classes can also be done in the primary constructor directly, which makes the code even more concise. Like in all methods, Kotlin supports default parameters for parameters, set with “=
”.
class Person(val name: String, val age: Int = 50)
Same as with local variables, instead of val
, a property can be declared mutable using var
instead. Note that you don’t have to write an empty class body if no content is defined.
Special Classes
Besides ordinary classes, Kotlin knows a few special class declarations, which are worth knowing. The following will give a quick overview.
data class |
Adds standard functionality for toString , equals , hashCode etc. |
sealed class |
Restricts class hierarchies to a set of subtypes. Useful with when |
Nested class |
Classes can be created in other classes, also known as “inner class” |
enum class |
Collect constants that can contain logic |
object declarations |
Used to create Singletons of a type |
Of course, Kotlin also supports inheritance through interface
s and abstract
classes.
Function Types and Lambdas
In order to be able to understand idiomatic Kotlin code, it’s essential to recognize how function types and especially lambdas look like. Just as you can declare variables of type Int
or String
, it’s also possible to declare variables of function types, e.g. (String) -> Boolean
.
val myFunction: (String) -> Boolean = { s -> s.length > 3 }
myFunction("HelloWorld")
The variable is declared as a function type that takes a String
argument and returns a Boolean
. The method itself is defined as a lambda enclosed in curly braces. In the shown lambda, the String
parameter is declared and named before the ->
symbol, whereas the body follows after it.
Lambda Special Syntax
The language designers decided on some special lambda features, which make the usage even more powerful.
it
: implicit name of single parametersIn many cases, lambdas are used with single parameters like in the previous example. In such situations, you don’t have to give the parameter an explicit name. Instead, the implicit name it
can be used.
val myFunction: (String) -> Boolean = { it.length > 3 }
myFunction("HelloWorld")
- For unused parameters, use
_
In some cases, it might be unnecessary to make use of every possible available parameter in a lambda. The compiler warns the developer about such unused variables, which can be avoided by naming it with an underscore.
val myFunction: (String, Int) -> Boolean = { s, _ -> s.length > 3 }
myFunction("HelloWorld", 42)
Higher-Order Functions
If a function takes another function as an argument or returns another function as its result, it’s called a higher-order function. Such functions are essential in Kotlin as many library functions rely on this concept. Let’s see an example.
fun main(args: Array<String>) {
myHigherOrderFun(2, { it.length > 2 })
}
fun myHigherOrderFun(iterations: Int, test: (String) -> Boolean){
(0 until iterations).forEach {
println("$it: ${test("myTestString")}")
}
}
The function myHigherOrderFun
defines two parameters, one of which is another function test
. The function takes test
and applies a String
to it multiple times depending on what the first argument iterations
is. By the way, the example uses a range to imitate an indexed for
loop here.
The shown main
function demonstrates the usage of a higher-order function by calling it with an anonymous function. The syntax looks a bit messy, which is why the language designers decided on a very important convention: If a lambda is the last argument to a function, it can be placed after the closing parentheses or, if it’s the only argument, the parentheses can be omitted completely like shown with forEach
above. The following snippet demonstrates this convention applied to an invocation of myHigherOrderFun
.
//Lambda after closing parentheses
myHigherOrderFun(2) {
it.length>2
}
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}