Fehlerbehandlung in Kotlin: try/catch und mehr – von A bis Z
Fehlerbehandlung ist eine der zentralen Aufgaben eines jeden Entwicklers. In Kotlin gibt es, wie in anderen modernen Sprachen, mehrere Ansätze zur Verwaltung von Ausnahmesituationen. In diesem Artikel betrachten wir alle grundlegenden Methoden: vom klassischen try/catch bis zu fortgeschrittenen Techniken mit Result, sealed class und Nothing. Sie erfahren, wie Sie zuverlässigen und lesbaren Code schreiben, der Fehler korrekt behandelt.
1. Der klassische try/catch/finally-Block
Der grundlegende Mechanismus zur Ausnahmebehandlung in Kotlin ist der try/catch-Block. Er funktioniert ähnlich wie in Java, jedoch mit einigen Verbesserungen. In Kotlin ist try ein Ausdruck, der einen Wert zurückgeben kann.
fun parseNumber(input: String): Int? { return try { input.toInt() } catch (e: NumberFormatException) { println("Parsing-Fehler: ${e.message}") null // gibt null bei Fehler zurück } finally { println("Der finally-Block wird immer ausgeführt") }}
fun main() { val result = parseNumber("123abc") println(result) // null}Wichtige Merkmale:
trykann als Ausdruck verwendet werden – das Ergebnis des letzten Ausdrucks imtry- odercatch-Block wird zum Ergebnis des gesamten Blocks.- Der
finally-Block wird in jedem Fall ausgeführt (auch wenn eine Ausnahme oder einreturnauftritt). - In Kotlin gibt es keine geprüften Ausnahmen (checked exceptions) wie in Java – das vereinfacht den Code.
Es ist wichtig zu bedenken, dass übermäßiger Gebrauch von try/catch den Code weniger lesbar macht. Verwenden Sie es nur für wirklich außergewöhnliche Situationen und nicht zur Steuerung des Programmablaufs.
2. Der Result-Typ und der funktionale Ansatz
Für eine elegantere Fehlerbehandlung bietet Kotlin den Typ Result<T>. Dies ist eine sealed class, die entweder Success (mit einem Ergebnis) oder Failure (mit einer Ausnahme) sein kann. Er eignet sich ideal für einen funktionalen Stil.
import kotlin.Result
fun divide(a: Int, b: Int): Result<Int> { return try { Result.success(a / b) } catch (e: ArithmeticException) { Result.failure(e) }}
fun main() { val result = divide(10, 0) // Verarbeitung mit when when (result) { is Result.Success -> println("Ergebnis: ${result.value}") is Result.Failure -> println("Fehler: ${result.exception.message}") } // Oder mit Methoden result.onSuccess { value -> println("Erfolg: $value") } .onFailure { e -> println("Fehlschlag: ${e.message}") } // Transformation val mapped = result.map { it * 2 } // wenn failure, bleibt es failure println(mapped)}Vorteile von Result:
- Zeigt explizit an, dass eine Funktion mit einem Fehler enden kann.
- Ermöglicht Methodenverkettungen (
map,flatMap,recover). - Erfordert kein
try/catchim aufrufenden Code.
Allerdings ist Result nicht für die Speicherung in Sammlungen oder die Übergabe über Coroutine-Grenzen hinweg gedacht – dafür verwenden Sie besser sealed class.
3. Sealed-Klassen für detaillierte Fehlerbehandlung
Wenn verschiedene Fehlertypen unterschiedlich behandelt werden müssen, ist sealed class die ideale Wahl. Sie können eine Fehlerhierarchie erstellen und diese in einem when mit vollständiger Abdeckung behandeln.
sealed class NetworkResult<out T> { data class Success<T>(val data: T) : NetworkResult<T>() data class NetworkError(val code: Int, val message: String) : NetworkResult<Nothing>() data class Timeout(val timeout