In Kotlin gibt es die sogenannten “Scope Functions” let, run, with, apply und also. Ihr Hauptzweck besteht darin, die Arbeit mit Objekten in Kotlin zu vereinfachen und den Code lesbarer zu gestalten indem sie es erlauben Codeblöcke in Form von Lambda-Ausdrücken auf bestimmten Objekten auszuführen und dabei den Kontext zu steuern.

Basically, these functions all perform the same action: execute a block of code on an object. What’s different is how this object becomes available inside the block and what the result of the whole expression is.

Dokumentation auf kotlinlang.org


Übersicht über die “Scope Functions”

  Zweck Beispiel Signatur
let Lambda ausführen auf obj / Ergebnis der Lambda-Expression liefern val result = obj.let { ... } inline fun <T, R> T.let(block: (T) -> R): R
run Execute on context object, return result val result = obj.run { ... } inline fun <T, R> T.run(block: T.() -> R): R
with Execute on any object, no need for it to be receiver val result = with(obj) { ... } inline fun <T, R> with(receiver: T, block: T.() -> R): R
apply Apply configurations, return modified object val modifiedObj = obj.apply { ... } inline fun <T> T.apply(block: T.() -> Unit): T
also Execute and return original object val result = obj.also { ... } inline fun <T> T.also(block: (T) -> Unit): T


let und run

Sowohl let als auch run liefern als Ergebnis den Wert der Lambda-Expression zurück.

let
    val sb = StringBuilder()
    val letRes: String = sb.let {
        // receiver "sb" wird der Lambda Expression übergeben und ist hier
        // implizit als "it" verfügbar.
        // Statt des impliziten "it" könnte ein eigener anders benannter Parameter verwendet werden.
        it.append(42)
        it.append("Let")
        it.toString()
    }
    println(letRes)     // 42Let

run

    var someValue = 42
    val runRes: String = run {
        // "run" ist eine "einfache" Funktion (ohne Parameter) und keine Methode.
        // "Run" führt einfach nur die Lambda Expression aus und liefert ihr
        // Ergebnis zurück. Vorteil ist, daß die mit "run" ausgeführte Lambda
        // einen eigenen Scope öffnet.
        val someValue = 142  // neue Definition von 'someValue' in diesem Kontext
        StringBuilder()
            .append(someValue)
            .append("Run")
            .toString()
    }
    // someValue ist hier wieder 42
    println(runRes)     // --> 42
    println(someValue)  // --> 142Run

with, apply und also

Sowohl with als auch apply sowie also ignorieren den Wert der Lambda-Expression und liefern stattdessen ihren ‘receiver’ (apply und also) bzw. ihren Parameter (with) zurück.

with

    // "with" ist eine "einfache" Funktion mit einem 'Objekt' als Parameter.
    // "with" führt die Lambda Expression aus, bindet den impliziten "this" Parameter an
    // den Parameter des "with"-Aufrufes und liefert ihr Ergebnis zurück.
    val withRes: String = with(StringBuilder()) {
        this.append(42)
        append("With")  // Wie auch in Methoden kann "this" auch entfallen
        toString()
    }
    println(withRes)   // 42With

apply

    val applyReceiver=StringBuilder()
    val sbRes : StringBuilder = applyReceiver.apply {
        this.append(42)
        append("Apply")  // Wie auch in Methoden kann "this" auch entfallen
        // das "Ergebnis" dieser Lambda Expression ist irrelevant
    }
    println(sbRes)  // 42Apply

also

    val alsoReceiver=StringBuilder()
    val sbRes2 : StringBuilder = alsoReceiver.also {
        // Es gibt hier im Gegensatz zu "apply" kein implizites "this"
        // Stattdessen wird "it" verwendet.
        // Statt des impliziten "it" könnte ein eigener anders benannter Parameter verwendet werden.
        it.append(42)
        it.append("Also")  // Wie auch in Methoden kann "this" auch entfallen
        // das "Ergebnis" dieser Lambda Expression ist irrelevant
    }
    println(sbRes2)   // 42Also