Skip to content

02 - Kotlin

Variables

Variable (mutable) - var xxx Constant (immutable) – val yyy

Primary Constructor

The primary constructor is part of the class header

1
2
3
4
5
class Person(val _firstName: String, val _lastName: String, var _age: Int) {
    fun info(){
        print("$_firstName $_lastName $_age")
    }
}

Initializer block(s)

The primary constructor has a constrained syntax, and cannot contain any code.

Initializer blocks are used for class setup

1
2
3
4
5
6
7
8
9
class Person(var _firstName: String, var _lastName: String, val _age: Int) {
    init {
        _firstName = _firstName.capitalize()
    }

    fun info(){
        print("$_firstName $_lastName $_age")
    }
}

Secondary Constructor

Must not overload primary (different signature)

Must call primary constructor (if primary is defined)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Person(var _firstName: String, var _lastName: String, val _age: Int) {
    init {
        _firstName = _firstName.capitalize()
    }

    fun info(){
        print("$_firstName $_lastName $_age")
    }

    // must call primary constructor
    constructor(_firstName1: String, _lastName1: String) : this(_firstName1, _lastName1, 99) {

    }
}

Companion Object

Companion objects are singleton objects whose properties and functions are tied to a class but not to the instance of that class

Use @JvmStatic annotation to get true static bytecode in jvm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Drink {
    companion object {
        const val WATER = "water"
    }

    fun bestDrink() = WATER
}

class X {
    init {
        val a = Drink.Companion.WATER
        val b = Drink.WATER
        val c = Drink().bestDrink()
    }
}

Nullable ref

  • var b: String? Vs var a: String
  • val l = if (b != null) b.length else -1
  • Safe calls with ?.
    • print(b?.length)
    • b?.let { print(it) }
  • Elvis operator - val l = b?.length ?: -1
  • val l = b!!.length - not-null assertion
  • Safe casts
    • val aInt: Int? = a as? Int

Scoping functions

Executing block of code within the context of object. Call scoping function on object providing lambda expression - temporary scope is formed. In this scope you can access the object without its name.

Scope functions are

  • let

let takes the object it is invoked upon as the parameter (it) and returns the result of the lambda expression.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun scopeLet(){
    var str ="This is test"
    var strLen = str.let { it.length }
    strLen++

    var str2: String? = null
    var strLen2 = str2?.let {
        it.length
    } ?: 0
}
  • run

Similar to the let function, the run function also returns the last statement.
Unlike let, the run function doesn’t support the it keyword.
The context object is available as a receiver (this).

1
2
3
4
5
6
7
fun scopeRun() {
    var name = "nothing here!"
    name = run {
        "RUN"
    }
    print(name) // RUN
}
  • with

A non-extension function: the context object is passed as an argument, but inside the lambda, it's available as a receiver (this). The return value is the lambda result. with is used to change instance properties without the need to call dot operator over the reference every time.
The last expression of with function returns a result.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fun scopeWith() {
    data class Person(var name: String, var tutorial: String)

    var person = Person("Anupam", "Kotlin")
    var xyz = with(person) {
        name = "No Name"
        tutorial = "Swift"
        val xyz = "return value"
        xyz
    }
    println(xyz)
}
  • apply

Unlike let, it returns the original object instead of any new return data. Hence the return data has always the same type.

Runs on the object reference (receiver) in the expression and returns the object reference on completion.

1
2
3
4
5
6
7
8
9
fun scopeApply() {
    data class Person(var name: String, var tutorial: String)

    var person = Person("akaver", "Kotlin")
    person.apply {
        this.tutorial = "Swift"
    }
    println(person)
}
  • also

also expressions does some additional processing on the object it was invoked. Unlike let, it returns the original object instead of any new return data. Hence the return data has always the same type. Like let, also uses it too.

1
2
3
4
5
fun scopeAlso(){
    var m = 1
    m = m.also { it + 1 }.also { it + 1 }
    println(m) // 1 
}

Scope functions, overview

Kotlin

  • Executing a lambda on non-null objects: let
  • Introducing an expression as a variable in local scope: let
  • Object configuration: apply
  • Object configuration and computing the result: run
  • Running statements where an expression is required: non-extension run
  • Additional effects: also
  • Grouping function calls on an object: with

takeIf, takeUnless

In addition to scope functions, the standard library contains the functions takeIf and takeUnless. These functions let you embed checks of the object state in call chains.

1
2
3
4
fun takeIf() {
    var number = 2
    val evenOrNull = number.takeIf { it % 2 == 0 }
}

Returns null on negative/other case