Android Coden
Android 6 min lesen

Nullable Types in Kotlin

Nullable Types schützen dich vor vielen Null-Crashes. Du lernst, wann ein Wert fehlen darf und wie du damit sauber umgehst.

Null-Crashes gehören zu den klassischen Fehlern in Android-Apps: Ein Wert fehlt, dein Code behandelt ihn aber so, als wäre er sicher vorhanden. Kotlin löst dieses Problem nicht durch Disziplin allein, sondern durch das Typsystem. Du siehst direkt am Typ, ob ein Wert fehlen darf, und der Compiler zwingt dich, damit bewusst umzugehen.

Was ist das?

Nullable Types sind Kotlin-Typen, die null enthalten dürfen. Du erkennst sie am Fragezeichen: String? bedeutet, dass der Wert entweder ein String oder null sein kann. Ohne Fragezeichen ist der Typ non-null. Ein String darf also nicht null sein, sofern du nicht über unsichere Umwege aus Java oder Plattform-APIs kommst.

Das mentale Modell ist wichtig: Nullable ist keine Sonderbehandlung am Rand, sondern ein Teil der Typinformation. Wenn eine Funktion User? zurückgibt, sagt sie dir: “Es kann sein, dass kein User vorhanden ist.” Wenn sie User zurückgibt, verspricht sie: “Du bekommst einen User.” Diese Unterscheidung macht Code lesbarer und verhindert viele Fehler schon vor dem Start der App.

Im Android-Kontext ist das besonders relevant, weil viele Datenquellen unvollständig sein können. Ein Intent-Extra kann fehlen. Ein Bundle kann keinen Wert für einen Key enthalten. Eine API-Antwort kann optionale Felder liefern. Ein Repository kann noch keinen Cache-Eintrag haben. Auch in Jetpack Compose modellierst du UI-State oft mit Werten, die während des Ladens fehlen können, etwa profile: Profile?.

Nullable Types sind daher ein Baustein für robuste Android-Architektur. Sie helfen dir, Zustände explizit zu machen, statt implizit auf “wird schon da sein” zu setzen. Genau dadurch wird Code besser testbar und leichter im Code-Review zu prüfen.

Wie funktioniert es?

Kotlin unterscheidet auf Typ-Ebene zwischen nullable und non-null. Du kannst eine Variable vom Typ String nicht direkt auf null setzen. Für String? ist das erlaubt. Sobald du einen nullable Wert verwenden willst, blockiert der Compiler direkte Zugriffe, die bei null abstürzen könnten.

Ein Beispiel: Auf name.length darfst du nur zugreifen, wenn name non-null ist. Bei name: String? musst du eine sichere Strategie wählen. Typische Werkzeuge sind ein if-Check, der Safe-Call-Operator ?., der Elvis-Operator ?: oder ein früher Rücksprung mit return.

Der Safe Call user?.name führt den Zugriff nur aus, wenn user nicht null ist. Sonst wird das Ergebnis ebenfalls null. Der Elvis-Operator liefert einen Ersatzwert: user?.name ?: "Gast". Dadurch kannst du eine klare fachliche Entscheidung ausdrücken: Was soll passieren, wenn der Wert fehlt?

Kotlin kann nach einer Prüfung häufig automatisch erkennen, dass ein Wert nicht mehr nullable ist. Das nennt man Smart Cast. Nach if (name != null) behandelt Kotlin name im jeweiligen Block als String, solange der Wert nicht verändert werden kann. Bei veränderlichen Properties oder nebenläufigem Zugriff ist Kotlin vorsichtiger, weil sich der Wert zwischen Prüfung und Zugriff ändern könnte.

Der gefährlichste Operator in diesem Bereich ist !!. Er wandelt einen nullable Wert in einen non-null Wert um, wirft aber eine Exception, wenn der Wert doch null ist. Du sagst damit dem Compiler: “Ich weiß es besser.” Manchmal ist das vertretbar, zum Beispiel in sehr eng kontrolliertem Testcode. In App-Code ist es meistens ein Warnsignal. Wenn du !! brauchst, fehlt oft eine saubere Modellierung oder eine klare Fehlerbehandlung.

Auch Java-Interop spielt in Android eine Rolle. Viele Android-SDK-APIs stammen historisch aus Java. Kotlin erkennt dort nicht immer eindeutig, ob ein Wert nullable oder non-null ist. Solche Platform Types verlangen besondere Aufmerksamkeit. Prüfe Rückgabewerte aus älteren APIs lieber explizit, statt dich auf Annahmen zu verlassen.

In der Praxis

Nimm eine Detailseite, die eine User-ID aus einem Intent bekommt. Diese ID kann fehlen, wenn die Activity falsch gestartet wurde oder ein Testfall unvollständig ist. In Kotlin solltest du diesen Fall nicht ignorieren, sondern früh behandeln.

class UserDetailActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val userId: String? = intent.getStringExtra("user_id")

        if (userId == null) {
            finish()
            return
        }

        loadUser(userId)
    }

    private fun loadUser(userId: String) {
        // userId ist hier non-null und kann sicher verwendet werden.
    }
}

Der entscheidende Punkt ist nicht die konkrete Activity, sondern die Grenze im Code. Direkt nach dem Lesen aus dem Intent ist userId nullable. Nach dem Check ist klar: Ohne ID kann diese Seite nicht sinnvoll arbeiten. Der frühe Rücksprung hält den Rest der Funktion sauber. loadUser bekommt deshalb einen non-null String und muss sich nicht erneut mit demselben Fehlerfall beschäftigen.

In Compose sieht das ähnlich aus, nur oft im UI-State. Ein Screen könnte während des Ladens noch kein Profil haben:

data class ProfileUiState(
    val isLoading: Boolean = false,
    val profile: Profile? = null,
    val errorMessage: String? = null
)

@Composable
fun ProfileScreen(state: ProfileUiState) {
    when {
        state.isLoading -> LoadingView()
        state.errorMessage != null -> ErrorView(state.errorMessage)
        state.profile != null -> ProfileContent(state.profile)
        else -> EmptyView()
    }
}

Hier macht Nullable den Zustand sichtbar: profile kann fehlen. Trotzdem solltest du aufpassen, dass nicht zu viele nullable Properties unklare Kombinationen erlauben. Wenn isLoading == true, profile != null und errorMessage != null gleichzeitig möglich sind, wird der UI-State schwer zu verstehen. Für größere Screens ist oft ein sealed Interface mit klaren Zuständen besser. Das gehört dann zur Architekturentscheidung, aber Nullable Types helfen dir bereits, die Schwachstelle zu erkennen.

Eine praktische Entscheidungsregel: Nutze nullable Typen nur dann, wenn “kein Wert” fachlich ein echter Zustand ist. Wenn ein Wert immer vorhanden sein muss, mache ihn non-null und sorge dafür, dass er beim Erzeugen des Objekts gesetzt wird. Ein User mit val id: String ist stärker als ein User mit val id: String?, wenn ein User ohne ID in deiner App keinen Sinn ergibt.

Eine typische Stolperfalle ist das Verschieben von Unsicherheit. Wenn du überall String? weiterreichst, wird jeder Aufrufer gezwungen, denselben Fall erneut zu behandeln. Besser ist oft: Prüfe den Wert an der Systemgrenze, entscheide dort, und arbeite danach mit non-null Typen weiter. Systemgrenzen sind zum Beispiel Intent-Daten, Netzwerkantworten, Datenbank-Migrationen oder Eingaben aus Formularen.

Eine zweite Stolperfalle ist ein Ersatzwert, der Fehler verdeckt. userId ?: "" verhindert zwar einen Crash, kann aber später zu einem falschen Request, leerer Anzeige oder schwer auffindbarem Bug führen. Ein Ersatzwert ist nur gut, wenn er fachlich korrekt ist. Für einen optionalen Anzeigenamen kann "Unbekannt" passen. Für eine technische ID ist ein früher Abbruch oder eine Fehlermeldung meist besser.

Beim Testen kannst du dein Verständnis direkt prüfen. Schreibe Unit-Tests für Funktionen, die nullable Eingaben bekommen: einmal mit gültigem Wert, einmal mit null. Prüfe, ob die Funktion kontrolliert reagiert. Im Debugger lohnt es sich, an der Stelle zu stoppen, an der ein Wert von nullable zu non-null wird. Im Code-Review solltest du bei jedem ?, ?: und besonders bei !! fragen: Ist dieser fehlende Wert fachlich erlaubt, und ist die Reaktion darauf korrekt?

Fazit

Nullable Types geben dir in Kotlin ein klares Werkzeug gegen viele Null pointer crashes: Du modellierst fehlende Werte sichtbar, prüfst sie an sinnvollen Stellen und hältst den restlichen Code so non-null wie möglich. Übe das an echten Android-Grenzen wie Intent-Extras, Bundles und UI-State, schreibe Tests für den null-Fall und markiere jedes !! im Code-Review als Begründungspflicht.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.