Das Milliarden-Dollar-Problem
In vielen Sprachen führt null zu Abstürzen (NullPointerException). Kotlin macht Nullbarkeit zum Teil des Typs: String kann niemals null sein, String? schon. Der Compiler erzwingt die Prüfung.
fun main() {
val a: String = "Text"
val b: String? = null
// val c: String = null // Fehler: Null kann kein Wert vom Typ String sein
println(a.length)
// println(b.length) // Fehler: nur sicherer Aufruf mit ?. erlaubt
println(b?.length) // sicherer Aufruf: null statt Absturz
println(b ?: "Standard") // Elvis-Operator: Ersatz bei null
println(b?.length ?: 0)
}Ausgabe
4 null Standard 0
Der Compiler lehnt null für einen nicht nullbaren Typ ab:
fun main() {
val c: String = null
}Fehlermeldung
main.kt:2:21: error: null can not be a value of a non-null type String
Die Werkzeuge
| Syntax | Bedeutung |
|---|---|
Typ? | nullbarer Typ |
x?.foo() | ruft foo nur auf, wenn x != null, sonst null |
x ?: y | Elvis: x, falls nicht null, sonst y |
x!! | behauptet "nicht null", wirft sonst NullPointerException (vermeiden!) |
x as? T | sicherer Cast, null bei Fehlschlag |
x?.let { ... } | Block nur ausführen, wenn nicht null |
data class Adresse(val ort: String?)
data class Kunde(val name: String, val adresse: Adresse?)
fun ortVon(k: Kunde?): String = k?.adresse?.ort ?: "unbekannt" // Kette sicherer Aufrufe
fun main() {
println(ortVon(Kunde("Mia", Adresse("Berlin"))))
println(ortVon(Kunde("Tom", null)))
println(ortVon(null))
val eingabe: String? = "42"
eingabe?.let { println("Zahl: ${it.toInt() + 1}") }
val x: Any = "Text"
val zahl = x as? Int
println(zahl)
val ergebnis = try { null!! } catch (e: NullPointerException) { "NPE gefangen" }
println(ergebnis)
}Ausgabe
Berlin unbekannt unbekannt Zahl: 43 null NPE gefangen
Smart Casts
Nach einer Null-Prüfung (oder is-Prüfung) behandelt der Compiler die Variable automatisch als nicht null bzw. als den geprüften Typ:
fun laenge(s: String?): Int {
if (s == null) return 0
return s.length // hier ist s automatisch String
}
fun beschreibe(x: Any): String {
if (x is String && x.length > 3) return "langer Text: ${x.toUpperCase()}"
if (x is Int) return "Zahl + 1 = ${x + 1}"
return "anderes"
}
fun main() {
println(laenge(null))
println(laenge("Kotlin"))
println(beschreibe("Hallo"))
println(beschreibe(41))
println(beschreibe(2.5))
val liste: List<String?> = listOf("a", null, "b")
println(liste.filterNotNull())
println(liste.map { it ?: "-" })
}Ausgabe
0 6 langer Text: HALLO Zahl + 1 = 42 anderes [a, b] [a, -, b]
lateinit und lazy
class Dienst {
lateinit var name: String // wird später initialisiert (nicht null-Typ)
val schwer: String by lazy { // wird erst beim ersten Zugriff berechnet
println("berechne ...")
"Ergebnis"
}
fun istGesetzt() = ::name.isInitialized
}
fun main() {
val d = Dienst()
println(d.istGesetzt())
d.name = "A"
println(d.istGesetzt())
println(d.schwer)
println(d.schwer)
}Ausgabe
false true berechne ... Ergebnis Ergebnis
Merke
Typist nie null,Typ?kann null sein?.sicherer Aufruf,?:Elvis,?.let { }für Blöcke!!vermeiden: besser prüfen oder?:nutzen- Smart Casts nach
== nullundis lateinitundby lazyfür verzögerte Initialisierung
Aufgabe
Schreibe eine Funktion ersterBuchstabe(s: String?): Char? und gib "-" aus, wenn nichts vorhanden ist.