How to read Kotlin without freaking out

Switching from Java Lang to Kotlin, reading and understanding Kotlin, can be challenging for the first time. The very concise nature of the syntax used by Kotlin can be irritating and off-putting. It might even lead to not wanting to try Kotlin. In my opinion, this should not be the case. Little insights and tips can help overcome the first barrier. I am convinced that even the most hardened Java programmers can understand Kotlin easily. This article aims to clarify most of the strange syntax and features of Kotlin, which you will probably encounter the most. You might still not like Kotlin syntax, but at least you won’t have a hard time reading it.

Kotlin

Type declaration

val name1: String = "John"
val name2 = "John"
name2 = "Eric" // COMPILATION ERROR

var name3 = "Jane"
name3 = "Janice"

fun greet(name: String): String {
    return "Hello " + name
}

You declare the type of a variable and functions after the variable name with a colon. You declare a variable with val (for final variables) or var (for non-final variables). Most of the time the type is inferred and you do not need to declare the type explicitly.

String templates

fun greet1(name: String) : String {
    return "Hello $name"
}

fun greet2(name: String) : String {
    return "Hello ${name.uppercase()}"
}

fun greet3(name: String) : String {
    return "Hello $name.uppercase()"
}

println(greet1("John"))   // Hello John
println(greet2("John")    // Hello JOHN
println(greet3("John"))   // Hello John.uppercase()

You can use variables in building strings using the dollar sign. Add curly braces for invocations.

Nullability

fun greet1(name: String): String {<br>return "Hello $name"<br>}

fun greet2(name: String?): String {
return "Hello $name" // COMPILATION ERROR
}

fun greet3(name: String?): String {=
return "Hello ${name?.uppercase()}"
}

fun greet4(name: String?): String {
return "Hello ${name!!.uppercase()}"
}

fun greet5(name: String?): String {
return "Hello ${name?.uppercase() ?: "Jane Doe"}"
}

greet1(null) // COMPILATION ERROR
greet2(null) // COMPILATION ERROR in method
greet3(null) // Hello null
greet4(null) // NullPointerException at runtime
greet5(null) // Hello Jane Doe

You will see a lot of question marks. Kotlin has normal types and nullable types. For every type, there exists a nullable type. Like String and String?. The first type can never be null and the second type can be a String or null. Nullable types have to be checked. This can be done by adding? at the end of the type declaration.

If you are daring and you are sure the nullable type can never be null, use the double exclamation mark operator „!!“.
The elvis operator ?: states what has to be done if the value is null.

Lambdas with curly braces

fun greet(name: String, greeting: (String) -> String): String {
return greeting(name)
}
greet("John", { x -> "Hello $x" })
greet("John") { x -> "Hello $x" }
greet("John") { "Hello $it" }
  • You can define functions as parameters. If the function is the last argument, you can pull this lambda out of the parentheses. Both examples are the same.
  • If the lambda takes one argument, you can exchange it for a special keyword ‚it‘
  • A lambda is always defined inside curly braces.

Lists and the keyword it

val numbers = listOf(1, 2, 3, 4, 5)

numbers.filter({ n -> n % 2 == 0 })
numbers.filter { n -> n % 2 == 0 }
numbers.filter { it % 2 == 0 }

Here is an example of the same filter, but written more concisely each time. The last one is the recommended use.

  • There is no need for .stream(). You can call .map() or .filter() on a Collection directly
  • Because the last parameter of map, filter, etc is the lambda, you can take out of the normal braces
  • The special keyword ‚it‘ can also be used
  • There is no need for .collect() or .toList() at the end of a stream

Extension functions

You can use one of the parameters of a method as a receiver of the method. These methods are called extension functions.
Instead of

fun add(x: Int, y: Int): Int {
    return x + y
}

you can do

fun Int.add(y: Int): Int {
    return this + y
}

you can call the last function as

3.add(5) // returns 8

Extension functions are used a lot. If done well, it enhances readability. The need for Util-classes is minimized.

Infix function

If the function is an infix function, then you can invoke the function without a dot and without braces. Like so

infix fun add(x: Int, y: Int): Int {
    return x + y
}

3 add 5 // returns 8

Function body as expression body

If the function body is an expression, you can remove the curly braces and replaces them with an equal sign. You can also remove the return type.

fun add(x: Int, y: Int): Int {
    return x + y
}

can be written as:

fun add(x: Int, y: Int) = x + y

Constructors

The constructor is defined in parentheses in the class header. This is called the primary constructor. You can also define other constructors in the class body.

class Person constructor(firstName: String, lastName: String ) {}

If the primary does not have any modifiers or annotations the keyword constructor can be omitted.

class Person(firstName: String, lastName: String) {}

Declare properties directly in the constructor by adding val or val in front of the declaration.

class Person(val firstName: String, lastName: String) {}

fun main() {
val person = Person("John", "Doe")
val nameJohn = person.firstName<br>println(nameJohn) // prints "John"
}

Accessing and encapsulating properties

class Address() {
    var street: String = "Baker Street"
    var city: String = "London"
        get() = field.toUpperCase() // field is a special keyword
    var zipcode: String = "12345"
        set(value) { field = "UK" + value }
}

fun main() {
    val address = Address()
    println(address.street) // prints "Baker Street"
    println(address.city) // prints "LONDON"
    address.street = "Another street"
    println(address.street) // prints "Another street"
    println(address.zipcode) // prints "12345"
    address.zipcode = "567"
    println(address.zipcode) // prints "UK567"
}

You can access and set properties just by calling their variable name. Use set() and get() to add further features for getting and setting properties.

Scoping functions

Scoping functions have the purpose of executing a block of code within the context of an object.

let

The context object is available as an argument (it). The return value is the lambda result.let can be used to invoke one or more functions on the results of call chains.

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

apply

val adam = Person("Adam").apply { 
    age = 20                       // same as this.age = 20
    city = "London"
}

with

Use with for calling functions on the context object without providing the lambda result. In the code, with can be read as „with this object, do the following.

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

run

run does the same with but invokes as let – as an extension function of the context object. run is useful when your lambda contains both the object initialization and the computation of the return value.

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}

// the same code written with let() function:
val letResult = service.let {
    it.port = 8080
    it.query(it.prepareRequest() + " to port ${it.port}")
}

also

also is good for performing some actions that take the context object as an argument. Use also for actions that need a reference to the object rather than its properties and functions, or when you don’t want to shadow the this reference from an outer scope.

val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")

Quellen

  • Kotlin, https://kotlinlang.org/
  • Baeldung Lambda Expression, https://www.baeldung.com/kotlin/lambda-expressions

Jelle van der Zwaag

Jelle is an Oracle Certified Java Professional and works as a Senior IT Consultant for NEOZO. He has a passion for writing concise and clean code and shines in complex distributed software projects.

Schreibe einen Kommentar