Jailhouse Rock

@NonNull und @Nullable in Kotlin

Ein Hauptaugenmerk von Kotlin im Unterschied zu Java dürfte auf dieser ganz bestimmten Eigenschaft liegen: Der Null- (bzw. Nicht-Null-) Sicherheit. Das bedeutet: Jede Referenz in Kotlin muss bereits bei der Definition gesetzt werden und wird im Compiler geprüft. Das heißt dann wohl: Adieu, NPE und Null-checks… ?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
val darfNullSein: Object // so nicht erlaubt!
val darfNullSein: Object // so nicht erlaubt!
val darfNullSein: Object   // so nicht erlaubt!
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
val darfNullSein = "" // In diesem Fall wird der Typ automatisch als String gesetzt.
val darfNullSein = "" // In diesem Fall wird der Typ automatisch als String gesetzt.
val darfNullSein = ""      // In diesem Fall wird der Typ automatisch als String gesetzt.

Man kann natürlich zulassen, dass dennoch null-Werte gesetzt werden, wenn man es so definiert, doch auch das muss explizit zugewiesen werden:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
val darfNullSein: Object? = null // entspricht Java: “public @Nullable Object darfNullSein = null;”
val darfNullSein: Object? = null // entspricht Java: “public @Nullable Object darfNullSein = null;”
val darfNullSein: Object? = null   // entspricht Java: “public @Nullable Object darfNullSein = null;”

Möchte man dieses object später im Code verwenden, muß man seine Methoden mit einem zusätzlichen Fragezeichen vor dem Punkt aufrufen:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
darfNullSein?.toString() // in Java: “darfNullSein == null ? null : darfNullSein.toString();”
darfNullSein?.toString() // in Java: “darfNullSein == null ? null : darfNullSein.toString();”
darfNullSein?.toString()   // in Java: “darfNullSein == null ? null : darfNullSein.toString();”

Damit wird automatisch ein Null-Check durchgeführt. Ist object == null, wird null zurückgegeben, ansonsten das Ergebnis des Methodenaufrufs. Dies läßt sich auch beliebig verketten:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
darfNullSein?.method1()?.method2()?.method3()?. …
darfNullSein?.method1()?.method2()?.method3()?. …
darfNullSein?.method1()?.method2()?.method3()?. … 

Mit dem sogenannten “Elvis-Operator”

?:
?: kann auch ein anderer Standardwert als
null
null zurückgegeben werden. (Wer die Referenz nicht gleich versteht: einfach den Operator als Emoticon lesen und die Haartolle bewundern …)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
darfNullSein?.toString() ?: ""
darfNullSein?.toString() ?: ""
darfNullSein?.toString() ?: ""

Eine radikalere Methode stellt der folgende Aufruf dar:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
darfNullSein!!.toString() // vgl. Java: “Objects.requireNonNull(darfNullSein);”
darfNullSein!!.toString() // vgl. Java: “Objects.requireNonNull(darfNullSein);”
darfNullSein!!.toString()   // vgl. Java: “Objects.requireNonNull(darfNullSein);”

Dieser führt dazu, daß das Objekt an dieser Stelle nicht!! null!! sein!! darf!! Ist es das trotzdem, dann wird doch noch die berüchtigte NullPointerException freigelassen und das Programm bricht ab.

Zuletzt gibt es noch die Möglichkeit, die Zuweisung einer Variablen aufzuschieben, falls während des Konstruktors noch nicht alle Voraussetzungen erfüllt sind und sie erst später im Lebenszyklus der Klasse gesetzt werden kann. Dafür gibt es das Schlüsselwort

lateinit
lateinit. Das funktioniert allerdings nicht mit Immutables, d.h. nur bei
var
var, nicht
val
val
.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
lateinit var wirdSpäterGefüllt: Object // darf nicht null sein
lateinit var wirdSpäterGefüllt: Object // darf nicht null sein
lateinit var wirdSpäterGefüllt: Object  // darf nicht null sein

Spätestens ab hier ist man dann selbst dafür verantwortlich, dass die Initialisierung noch vor dem ersten Aufruf geschieht.