Expressions und Statements in Kotlin
Du lernst den Unterschied zwischen Expressions und Statements und nutzt ihn für klareren Android-Code.
Wenn du von Java, JavaScript oder einer anderen Sprache zu Kotlin kommst, wirkt Kotlin oft ungewöhnlich kompakt. Ein wichtiger Grund dafür ist der ausdrucksorientierte Stil: Viele Sprachkonstrukte liefern direkt einen Wert zurück. Wer Expressions und Statements sauber unterscheidet, schreibt Android-Code, der kürzer, besser prüfbar und oft leichter zu lesen ist. Gleichzeitig brauchst du ein gutes Gefühl dafür, wann Kompaktheit hilft und wann sie Logik versteckt.
Was ist das?
Eine Expression ist ein Ausdruck, der einen Wert hat. 42, "Hallo", user.name, price * amount und ein Funktionsaufruf wie repository.loadUser() sind typische Beispiele. Du kannst eine Expression meistens rechts von einer Zuweisung verwenden, als Argument an eine Funktion übergeben oder aus einer Funktion zurückgeben.
Ein Statement ist dagegen eine Anweisung. Es beschreibt, dass etwas passieren soll, ohne selbst als nützlicher Wert im Code weiterverwendet zu werden. In vielen Sprachen sind if, switch oder Schleifen vor allem Statements. Kotlin verschiebt diese Grenze: if und when sind in Kotlin Expressions. Sie können also nicht nur steuern, welcher Code ausgeführt wird, sondern auch einen Wert erzeugen.
Das ist für Android-Entwicklung sehr relevant. In ViewModels, Repositorys, Use Cases und Compose-UI triffst du ständig Entscheidungen: Welcher Text soll angezeigt werden? Welcher UI-State gilt? Welcher Fehler wird gemeldet? Welche Aktion folgt auf einen Netzwerkstatus? Wenn du solche Entscheidungen als Expressions formulierst, entsteht oft Code, der die fachliche Regel direkt zeigt.
Ein einfaches mentales Modell hilft: Frage dich bei jeder Codezeile, ob sie einen Wert berechnet oder nur einen Ablauf steuert. Wenn ein Konstrukt einen Wert berechnet, kannst du es meist besser benennen, testen und in kleinere Einheiten zerlegen. Wenn es nur Ablauf steuert, solltest du prüfen, ob dieser Ablauf wirklich dort hingehört oder ob du eine klarere Rückgabe formulieren kannst.
Wie funktioniert es?
Kotlin behandelt viele alltägliche Konstrukte als Expressions. Das wichtigste Beispiel ist if. In Kotlin kannst du den Wert eines if direkt einer Variable zuweisen. Beide Zweige müssen dann einen passenden Wert liefern. Dadurch wird klar: Diese Entscheidung berechnet ein Ergebnis.
Auch when ist eine Expression. Das ist besonders nützlich, wenn du mit Zuständen arbeitest, etwa mit einem sealed interface für Ladezustände. Ein when über alle bekannten Zustände kann dir helfen, vollständig zu bleiben. Wenn ein neuer Zustand hinzukommt, meldet der Compiler an vielen Stellen, dass ein Zweig fehlt. Diese Eigenschaft ist im Android-Alltag wertvoll, weil UI-State, Netzwerkstatus und Domain-Ergebnisse häufig als feste Zustandsmenge modelliert werden.
Nicht alles in Kotlin ist eine Expression. Zuweisungen liefern keinen Wert, und Schleifen wie for oder while sind keine Expressions. Sie dienen dem Ablauf. Wenn du aus einer Liste einen Wert erzeugen willst, nutzt du in Kotlin meistens Funktionen wie map, filter, fold, firstOrNull oder sumOf. Das passt gut zur Arbeitsweise mit unveränderlichen Daten und klaren Rückgabewerten.
Für Funktionen gibt es außerdem den Expression Body. Statt eines Blocks mit return kannst du eine Funktion direkt als Ausdruck schreiben:
fun displayName(user: User): String = user.nickname ?: user.name
Das ist sinnvoll, wenn die Funktion wirklich nur einen Wert berechnet und die Logik überschaubar bleibt. Bei längerer Logik ist ein Block mit lokalen Zwischennamen oft lesbarer.
In Coroutines und Flow taucht dasselbe Denken wieder auf. Ein map auf einem Flow beschreibt, welcher neue Wert aus einem alten Wert entsteht. Ein catch oder onEach beschreibt dagegen eher Ablauf oder Nebenwirkungen. In Compose ist die Trennung ebenfalls wichtig: Composables sollten UI aus State ableiten. Wenn du Anzeige-Text, Farben oder aktivierte Zustände als Expressions aus deinem State berechnest, bleibt die UI vorhersehbarer.
Der zentrale Punkt ist nicht, möglichst wenig Zeilen zu schreiben. Der Punkt ist, Rückgabewerte sichtbar zu machen. Android-Code wird schnell unübersichtlich, wenn Logik über mehrere veränderliche Variablen, frühe Seiteneffekte und verstreute Zuweisungen verteilt ist. Expressions helfen dir, eine Frage im Code direkt zu beantworten: Welcher Wert gilt hier?
In der Praxis
Stell dir vor, du baust eine Profilansicht in Compose. Dein ViewModel liefert einen ProfileUiState. Die UI muss daraus einen Titel, einen Status-Text und eine Schaltflächen-Aktivierung ableiten. Du kannst das mit veränderlichen Variablen und mehreren if-Blöcken schreiben. Klarer ist oft eine Expression, die den Wert direkt beschreibt.
sealed interface ProfileUiState {
data object Loading : ProfileUiState
data class Content(
val name: String,
val isPremium: Boolean
) : ProfileUiState
data class Error(val message: String) : ProfileUiState
}
fun titleFor(state: ProfileUiState): String =
when (state) {
ProfileUiState.Loading -> "Profil wird geladen"
is ProfileUiState.Content -> state.name
is ProfileUiState.Error -> "Profil nicht verfügbar"
}
fun actionEnabled(state: ProfileUiState): Boolean =
when (state) {
ProfileUiState.Loading -> false
is ProfileUiState.Content -> state.isPremium
is ProfileUiState.Error -> false
}
In diesem Beispiel ist when keine reine Ablaufsteuerung. Es ist eine Expression, die einen konkreten Rückgabewert erzeugt. Dadurch kannst du die Funktionen leicht testen: Für jeden Zustand erwartest du einen bestimmten String oder Boolean. Das ist ein großer Vorteil gegenüber Logik, die direkt in einer Composable mit mehreren lokalen var-Zuweisungen verteilt ist.
In Compose könntest du diese Funktionen dann nutzen:
@Composable
fun ProfileHeader(state: ProfileUiState, onActionClick: () -> Unit) {
Column {
Text(text = titleFor(state))
Button(
onClick = onActionClick,
enabled = actionEnabled(state)
) {
Text("Vorteile anzeigen")
}
}
}
Die Composable bleibt dadurch auf Darstellung konzentriert. Die Entscheidung, welcher Titel oder welcher Aktivierungszustand gilt, ist ausgelagert und besitzt einen Rückgabewert. Das passt zu modernem Android: UI reagiert auf State, und Logik wird so formuliert, dass sie separat prüfbar ist.
Eine typische Stolperfalle ist übertriebene Verschachtelung. Nur weil if und when Expressions sind, solltest du nicht jede Entscheidung in eine einzige lange Zeile pressen. Wenn eine Expression mehrere fachliche Regeln, Null-Prüfungen und Seiteneffekte mischt, wird sie schwerer lesbar als ein sauber benannter Block.
Schlecht lesbar wäre zum Beispiel:
val label = if (user != null) if (user.isPremium) "Premium" else if (user.name.isBlank()) "Gast" else user.name else "Unbekannt"
Der Code ist kurz, aber du musst ihn innerlich auseinandernehmen. Besser ist eine klare when-Expression mit Bedingungen:
val label = when {
user == null -> "Unbekannt"
user.isPremium -> "Premium"
user.name.isBlank() -> "Gast"
else -> user.name
}
Noch besser kann eine eigene Funktion sein, wenn die Regel an mehreren Stellen gebraucht wird:
fun labelFor(user: User?): String =
when {
user == null -> "Unbekannt"
user.isPremium -> "Premium"
user.name.isBlank() -> "Gast"
else -> user.name
}
Eine praktische Entscheidungsregel lautet: Nutze Expressions, wenn du eine fachliche Frage mit einem Wert beantwortest. Nutze Statements, wenn du einen Ablauf mit klaren Nebenwirkungen beschreibst, etwa Logging, Navigation oder das Starten einer einmaligen Aktion. Mische beides nicht unnötig. Eine Funktion, die einen Wert berechnet, sollte möglichst keine versteckten Nebenwirkungen haben. Eine Funktion, die eine Aktion ausführt, sollte nicht so tun, als wäre sie nur eine harmlose Berechnung.
Auch return verdient Aufmerksamkeit. In Kotlin kannst du frühe Rückgaben nutzen, wenn sie Lesbarkeit verbessern:
fun validateName(input: String): String? {
if (input.isBlank()) return "Der Name darf nicht leer sein"
if (input.length < 3) return "Der Name ist zu kurz"
return null
}
Das ist kein schlechter Stil. Für Validierungen kann es sehr klar sein. Wenn du aber aus mehreren Bedingungen einen einzigen Wert ableitest, ist eine when-Expression oft passender:
fun validateName(input: String): String? =
when {
input.isBlank() -> "Der Name darf nicht leer sein"
input.length < 3 -> "Der Name ist zu kurz"
else -> null
}
Beide Varianten sind gültig. Der Unterschied liegt in der Absicht. Die erste Variante liest sich wie eine Prüfung mit Ausstiegspunkten. Die zweite liest sich wie eine Wertberechnung. In Code-Reviews solltest du nicht nach der kürzesten Variante suchen, sondern nach der Variante, deren Absicht am klarsten ist.
Bei Flow und Coroutines gilt dieselbe Regel. Wenn du einen Datenstrom umwandelst, ist map eine gute Expression-orientierte Wahl:
val titles: Flow<List<String>> =
profiles.map { list ->
list.map { profile -> profile.name }
}
Wenn du dagegen eine Nebenwirkung auslöst, etwa ein Ereignis loggst, sollte das im Code sichtbar bleiben. Versteckte Nebenwirkungen in Expressions erschweren Tests und Fehlersuche.
val titles: Flow<List<String>> =
profiles.onEach { list ->
logger.log("Profile geladen: ${list.size}")
}.map { list ->
list.map { profile -> profile.name }
}
Hier ist besser erkennbar, was Wertumwandlung ist und was Nebeneffekt ist. Genau diese Trennung brauchst du in professionellem Android-Code, weil UI-State, Datenströme und Lebenszyklen sonst schwer nachvollziehbar werden.
Fazit
Expressions und Statements sind kein trockenes Sprachdetail, sondern ein Werkzeug für klaren Kotlin-Code. Wenn du erkennst, welche Konstrukte einen Rückgabewert liefern und welche nur Ablauf steuern, kannst du Android-Logik gezielter strukturieren: UI-State wird aus Daten abgeleitet, Entscheidungen werden testbar, und Kontrollfluss bleibt sichtbar. Übe das an kleinen Funktionen in deinem Projekt: Wandle eine verschachtelte if-Logik in eine when-Expression um, schreibe Unit-Tests für alle Zweige, setze im Debugger Haltepunkte auf die Rückgabewerte und prüfe im Code-Review, ob die kompakte Form wirklich lesbarer ist.