Java try...catch
Laufzeitfehler in Java mit try...catch-Blöcken abfangen und das Programm am Laufen halten, wenn etwas schiefläuft.
try/catch ist die kleinste Einheit der Ausnahmebehandlung in Java. Du umschließt den riskanten Code mit try und folgst ihm mit einem oder mehreren catch-Blöcken, die angeben, was bei einer bestimmten Art von Ausnahme zu tun ist. Zusammen bilden sie eine einzige Anweisung; du kannst nicht das eine ohne das andere haben (oder ohne ein finally, das wir in einem späteren Kapitel kennenlernen).
Aufbau
try {
// code that might throw
} catch (ExceptionType e) {
// code that runs only if a matching exception was thrown
}Einige Dinge sind auf den ersten Blick leicht zu übersehen:
- Die Variable im
catch(eper Konvention) ist auf den catch-Block beschränkt. Du kannst sie benennen, wie du möchtest;eundexsind die gängigen Bezeichnungen. - Der catch-Parameter ist effektiv final — du kannst ihn lesen, solltest ihn aber nicht neu zuweisen. Ihn als unveränderlich zu behandeln, macht den Code leichter nachvollziehbar.
- Der
try-Block hat seinen eigenen Gültigkeitsbereich. Variablen, die darin deklariert werden, sind weder für dencatchnoch nach der Anweisung sichtbar. Wenn du einen imtryberechneten Wert später benötigst, deklariere ihn außerhalb.
String content;
try {
content = Files.readString(path);
} catch (IOException e) {
content = ""; // works because `content` was declared outside the try
}
System.out.println(content);Welche catch-Blöcke greifen
Ein catch (T e)-Block wird ausgeführt, wenn die geworfene Ausnahme eine Instanz von T ist — einschließlich aller Untertypen von T. Also:
catch (Exception e)fängt fast alles ab:IOException,NullPointerException, eigene benutzerdefinierte Ausnahmen.catch (RuntimeException e)fängt Laufzeitfehler ab, aber keine geprüften Ausnahmen wieIOException.catch (NullPointerException e)fängt nur NPEs ab (und Unterklassen, von denen es typischerweise keine gibt).
Innerhalb des try wird nur der erste passende catch ausgeführt. Wenn du einen breiteren Typ vor einem engeren angibst, ist der engere catch unerreichbar und der Compiler verweigert dies:
try { ... }
catch (Exception e) { ... } // matches everything
catch (IOException e) { ... } // ERROR: unreachableListe catch-Blöcke immer vom Spezifischsten zum Allgemeinsten.
Was man in einem catch tun sollte
Ein catch-Block ist kein Ort, um Ausnahmen verschwinden zu lassen. Es ist ein Ort, um zu entscheiden, was bei einem bekannten Fehler zu tun ist. Die realistischen Optionen sind:
- Wiederherstellen — erneut versuchen, auf einen Standardwert zurückgreifen, auf eine andere Ressource wechseln. Das ist der beste Fall, und er ist seltener als man denkt.
- Protokollieren und erneut werfen — Details aufzeichnen und die Ausnahme an einen übergeordneten Handler weiterpropagieren, der weiß, was zu tun ist.
- Umhüllen und erneut werfen — eine low-level-Ausnahme in eine übersetzen, die zum Vokabular dieser Ebene passt (
IOException→ConfigLoadException). - In einen Rückgabewert übersetzen — wenn der Fehler wirklich erwartet wird (z. B. beim Parsen von Benutzereingaben),
Optional.empty()oder einen Sentinel-Wert zurückgeben.
Was man nicht tun sollte: eine Ausnahme abfangen und stillschweigend weitermachen, ohne sie zu protokollieren oder darauf zu reagieren. So verschwinden Fehler spurlos. Wir kommen darauf im Kapitel über Best Practices zurück.
Ausnahmeinformationen lesen
Der catch-Parameter ist ein Objekt. Drei Methoden, die du ständig verwenden wirst:
e.getMessage()— die menschenlesbare Beschreibung. Kannnullsein.e.toString()— Klassenname plus Nachricht, z. B.java.io.IOException: file not found.e.printStackTrace()— schreibt den Stack-Trace nachSystem.err. Praktisch beim Debuggen, aber verwende einen richtigen Logger in Produktionscode (er leitet den Trace durch denselben Kanal wie alles andere).
Eine vierte Methode, e.getCause(), gibt die zugrunde liegende Ausnahme zurück, wenn eine eingehüllt wurde — nützlich, wenn du den ursprünglichen Fehler innerhalb einer Übersetzungsschicht verstehen musst.
Exception abfangen vs. Throwable abfangen
Du kannst catch (Throwable t) schreiben und es fängt alles ab — einschließlich Errors wie OutOfMemoryError. Tu das nicht. Errors bedeuten, dass die JVM in Schwierigkeiten steckt und dein Code nicht in der Lage ist, sinnvoll darauf zu reagieren. Das Abfangen von Throwable verdeckt Fehler, die den Prozess zum Absturz bringen sollten.
Bleibe bei Exception oder einem ihrer Untertypen. Wenn du das Unerwartete wirklich protokollieren musst, bevor es stirbt, ist die richtige Form catch (RuntimeException e) { log; throw; }.
Ein ausgearbeitetes Beispiel
Ein kleines Hilfsprogramm, das eine Ganzzahl aus jeder Eingabezeile parst. Einige Zeilen sind gültige Zahlen, andere nicht, eine ist null. Wir verwenden try/catch, um über die fehlerhaften Zeilen hinwegzukommen und zu zählen, was passiert ist. Der Aufruf von line.trim() wirft eine NullPointerException beim null-Eintrag, während Integer.parseInt eine NumberFormatException wirft bei allem, was keine Ganzzahl ist — zwei verschiedene Ausnahmetypen, jeder mit seinem eigenen catch.
Die Ausgabe lautet:
not an integer: "hello"
null line — skipped
not an integer: "3.14"
---
parsed: 3, failed: 3, sum: 118
Zwei Dinge sind zu beachten. Erstens beendet sich das Programm ordnungsgemäß. Ohne die catch-Blöcke würde die erste fehlerhafte Zeile den Durchlauf beenden und nichts danach würde gezählt. Zweitens werden die zwei Fehler unterschieden: Die null-Zeile nimmt den NullPointerException-Zweig und gibt ihre eigene Nachricht aus, während "hello" und "3.14" den NumberFormatException-Zweig nehmen. Die Auswahl von catch-Blöcken nach Typ ist der Weg, verschiedene Fehler nicht durcheinander geraten zu lassen.
Was kommt als Nächstes
Ein catch ist der einfachste Fall. Echter Code muss normalerweise mehrere verschiedene Fehler gleichzeitig behandeln. Weiter zu Java mehrere catch-Blöcke und Multi-catch. Danach runden der finally-Block und try-with-resources die Syntax ab, während throw und throws die andere Seite abdecken: Fehler aus eigenem Code signalisieren.