Im Kotlin-Programmiersprachen wurden die Standard-Higher-Order-Funktionen apply, also, let und run eingeführt, um die chainable-Konfiguration von Objekten und lokale Änderungen mit minimalem Boilerplate zu vereinfachen. Diese Funktionen erleichtern die Arbeit mit veränderbaren und unveränderbaren Objekten, ermöglichen eine prägnante Ausdrucksweise von Transformationen und reduzieren einige Probleme mit der zeitlichen Sichtbarkeit von Variablen.
Diese Funktionen stammen aus dem Builder-Muster und der Fluent-Interface-Ansätze. Ihr Erscheinen ist dem Bemühen geschuldet, den Code sauberer zu gestalten und überflüssige Hilfsvariablen zu vermeiden.
Der herkömmliche Ansatz erfordert mehrfachen Zugriff auf das Objekt oder das Herausziehen temporärer Variablen. Dies verringert die Lesbarkeit und erhöht das Risiko von Fehlern:
val user = User() user.name = "Alex" user.age = 26 user.isActive = true
Die Verwendung von apply, also, let und run erhöht die Ausdruckskraft:
val user = User().apply { name = "Alex" age = 26 isActive = true }
Kurze Beschreibungen:
data class User(var name: String = "", var age: Int = 0, var isActive: Boolean = false) val configuredUser = User().apply { name = "Alice" age = 30 isActive = true } debugUser(configuredUser.also { println("User is konfiguriert: $it") }) val emailLength = configuredUser.email?.let { it.length } ?: 0 val description = configuredUser.run { "$name ($age)" }
Wichtige Merkmale:
Kann die Funktion let das Objekt, mit dem sie arbeitet, ändern?
Die Funktion let ist nicht dafür vorgesehen, das Objekt zu ändern. Sie dient eher dazu, eine Transformation auf einen Wert anzuwenden und gibt das Ergebnis zurück. Man sollte bedenken, dass innerhalb von let anstelle von this it verfügbar ist.
val upperName = user.name.let { it.uppercase() } // let ändert user nicht
Was ist der Unterschied zwischen apply und run?
Beide arbeiten mit dem Kontext this innerhalb der Lambda, der Unterschied liegt im Rückgabewert:
// apply val building = StringBuilder().apply { append("start-") append("end") } // building ist ein StringBuilder // run val result = StringBuilder().run { append("start-") append("end") toString() } // result ist ein String
Ist es möglich, apply und let zu verschachteln? Wenn ja, wann ist das sinnvoll?
Ja, die Verschachtelung wird sanft empfohlen, um Objekte zu aggregieren oder schrittweise zu konfigurieren, insbesondere bei der Arbeit mit nullable:
val userInfo = user?.apply { isActive = true }?.let { "${it.name} ist aktiv: ${it.isActive}" }
Im gesamten Code werden let zur Modifikation von Objekten verwendet, apply und let werden gemischt, sodass das Objekt dort, wo es erwartet wird, nicht geändert wird und sein Wert in Ketten verloren geht.
Vorteile:
Nachteile:
Es werden apply und also zur Konfiguration und Protokollierung verwendet, let nur für die Arbeit mit nullable und zum Erhalten von Ergebnissen, run — für Transformationen, die eine Berechnung eines neuen Wertes erfordern.
Vorteile:
Nachteile: