Was verbirgt sich hinter dem Fragezeichen? Eintauchen in nullable in Kotlin
\n\nHaben Sie jemals in Java programmiert und einen NullPointerException im unpassendsten Moment erwischt? Wenn ja – Sie wissen, wie schmerzhaft das ist. Die Kotlin-Entwickler haben dieses Problem ein für alle Mal gelöst, indem sie null safety nicht nur zu einer Funktion, sondern zur Philosophie der Sprache gemacht haben. Aber Anfänger verunsichert oft die Frage: "In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them?" (genau so lautet die populärste Frage auf Stack Overflow).
Heute analysieren wir alle Idiome der Arbeit mit nullable: vom einfachen ?. bis zum kniffligen let und dem magischen !!. Und das Wichtigste – wir schauen unter die Haube, um zu verstehen, wie das auf Bytecode-Ebene funktioniert. Los geht's!
Kurze Einführung: Was ist nullable?
\n\nIn Kotlin werden Typen in zwei Lager geteilt: nullable (können null sein) und non-nullable (können nicht null sein). Wenn Sie var name: String = null schreiben – gibt der Compiler einen Fehler aus. Aber var name: String? = null – ist ok. Das Fragezeichen ist ein Marker, der sagt: "Hey Compiler, ich weiß, dass hier null sein kann, schimpf nicht".
Aber wie arbeitet man damit? Einfach mit if auf null prüfen – das ist langweilig und nicht kotlinisch. Schauen wir uns die echten Werkzeuge an.
Das rettende Dreigestirn: ?, ?: und ?.
\n\nKotlin bietet drei Säulen, auf denen die gesamte Arbeit mit nullable ruht. Merken Sie sie sich – sie sparen Ihnen Stunden des Debuggens.
\n\n1. Safe call operator (?.)
\n\nDies ist der häufigste Gast in Ihrem Code. Der safe call-Operator erlaubt es, eine Methode oder Eigenschaft nur dann aufzurufen, wenn das Objekt nicht null ist. Ist das Objekt null – gibt der Operator null zurück, und keine Ausnahme.
\n\nval user: User? = null\nval name: String? = user?.name // name = null, kein Fehler\n\nUnter der Haube wird dies in Bytecode umgewandelt, der eine normale Nullprüfung und einen bedingten Aufruf durchführt. Keine Magie, nur Sicherheit.
\n\n2. Elvis operator (?:)
\n\nElvis ist Ihr Freund, wenn Sie einen Standardwert einsetzen müssen. Wenn der linke Teil nicht null ist – nehmen wir ihn, sonst – das, was rechts steht.
\n\nval name: String? = null\nval displayName: String = name ?: "Gast" // "Gast"\n\nElvis kann auch für den vorzeitigen Ausstieg aus einer Funktion verwendet werden:
\n\nfun getUserName(user: User?): String {\n val name = user?.name ?: return "Anonym"\n return name\n}\n\n3. Not-null assertion (!!)
\n\nDies ist der rebellische Operator. Er sagt: "Ich bin sicher, dass hier nicht null ist, und bin bereit zu sterben (im Sinne von, einen NPE zu werfen), wenn ich mich irre". Verwenden Sie ihn mit Vorsicht, nur wenn Sie 100% sicher sind.
\n\nval user: User? = getUserFromDatabase() // wir sind sicher, dass er existiert\nval name: String = user!!.name\n\nUnter der Haube wird !! in eine normale Prüfung mit dem Werfen eines NullPointerException bei null kompiliert. Nichts Kompliziertes, aber gefährlich.
Idiomatischer Weg: let, run, apply und Co.
\n\nJetzt kommen wir zum Interessantesten. Die Frage auf Stack Overflow handelt genau davon: wie man nullable-Werte idiomatisch umwandelt. Die Antwort – Erweiterungsfunktionen aus der Standardbibliothek.
\n\nlet – der König der nullable-Transformationen
\n\nDie Funktion let führt einen Codeblock nur aus, wenn das Objekt nicht null ist. Dabei ist das Objekt innerhalb des Blocks als it verfügbar (oder kann umbenannt werden).
val user: User? = getUser()\nuser?.let { \n println("Benutzername: ${it.name}")\n // Hier ist it garantiert nicht null\n}\n\nDies ist der idiomatische Weg, eine Aktion nur bei einem nicht-null Wert auszuführen. Ohne let müsste man schreiben:
if (user != null) {\n println("Benutzername: ${user.name}")\n}