Stufe 4Funktional denken · Lektion 21 von 34
Try, Success und Failure
Try verwandelt Code, der eine Exception werfen könnte, in einen gewöhnlichen Wert: Success mit dem Ergebnis oder Failure mit der Ausnahme. So lassen sich fehleranfällige Schritte verketten, ohne dass try-catch-Blöcke den Code zerreißen.
Try einfangen
import scala.util.{Try, Success, Failure}
val a = Try("42".toInt) // Success(42)
val b = Try("42x".toInt) // Failure(java.lang.NumberFormatException: For input string: "42x")Try(…) führt den Block aus. Läuft er durch, entsteht Success(ergebnis); wirft er eine Ausnahme, wird sie als Failure(ausnahme) gespeichert. Schwere Fehler wie OutOfMemoryError fängt Try bewusst nicht ab – nur „normale“ Ausnahmen im Sinne von NonFatal (siehe Exception Handling).
Auswerten
b match
case Success(n) => println(s"Zahl: $n")
case Failure(e) => println(s"Keine Zahl: ${e.getMessage}")
a.getOrElse(0) // 42
b.getOrElse(0) // 0
b.isFailure // true
b.toOption // None – der Fehlergrund geht dabei verlorenUmwandeln und verketten
Try bietet dieselben Werkzeuge wie Option: map wandelt ein erfolgreiches Ergebnis um, flatMap hängt einen weiteren Schritt an, der selbst scheitern kann.
def lesePort(s: String): Try[Int] = Try(s.trim.toInt)
def pruefePort(p: Int): Try[Int] =
if p > 0 && p < 65536 then Success(p)
else Failure(IllegalArgumentException(s"Port $p außerhalb des gültigen Bereichs"))
lesePort(" 8080 ").flatMap(pruefePort) // Success(8080)
lesePort("99999").flatMap(pruefePort) // Failure(… außerhalb des gültigen Bereichs)
lesePort("acht").flatMap(pruefePort) // Failure(NumberFormatException …)Sobald ein Schritt scheitert, werden alle weiteren übersprungen, und die erste Ausnahme wird durchgereicht. Mit einem for-Ausdruck sieht das so aus:
val port =
for
roh <- Try(sys.env("PORT"))
zahl <- lesePort(roh)
gueltig <- pruefePort(zahl)
yield gueltigFehler behandeln: recover und recoverWith
val mitStandard = port.recover {
case _: NoSuchElementException => 8080 // Variable nicht gesetzt: Standardport
}
val mitAlternative = port.recoverWith {
case _: NoSuchElementException => Try(scala.io.Source.fromFile("port.txt").mkString).flatMap(lesePort)
}recover ersetzt bestimmte Fehler durch einen Ersatzwert, recoverWith durch einen neuen Versuch, der wieder scheitern kann. Fehler, auf die kein case passt, bleiben erhalten. Beide nehmen eine partielle Funktion – dieselben case-Blöcke wie beim Pattern Matching.
Beide Fälle zusammenführen
val meldung: String = port.fold(
fehler => s"Konfiguration fehlerhaft: ${fehler.getMessage}",
p => s"Server startet auf Port $p"
)Anders als bei Option steht bei Try.fold der Fehlerfall zuerst.
Try und Ressourcen
scala.util.Using liefert sein Ergebnis direkt als Try. So lässt sich das Lesen einer Datei nahtlos in eine Kette einbauen:
import scala.util.Using
import scala.io.Source
def zeilen(pfad: String): Try[List[String]] =
Using(Source.fromFile(pfad))(_.getLines().toList)
zeilen("kunden.csv").map(_.drop(1)).map(_.size) // Anzahl Datenzeilen ohne KopfzeileWann Try, wann Either?
Try ist die richtige Wahl an der Grenze zu Code, der Exceptions wirft – Java-Bibliotheken, Dateizugriffe, Parser. Sein Fehlerfall ist aber immer ein Throwable, also recht grob. Wenn Sie eigene, fachliche Fehlerarten unterscheiden wollen (etwa „Kunde gesperrt“ oder „Guthaben zu gering“), ist Either besser geeignet. Der Übergang ist einfach: versuch.toEither liefert ein Either[Throwable, A].