Wozu dienen try-catch-Anweisungen?
Wie in anderen Programmen auch, so können in Java-Programmen natürlich Fehler auftreten. Nach ihrem Schweregrad werden sie in zwei Hauptkategorien eingeteilt:
- Errors sind Fehler, die meist durch Fehlfunktionen der JVM ausgelöst werden, zum sofortigen Abbruch der Programmausführung führen und nicht abgefangen werden.
- Exceptions (Ausnahmen) sind Fehler, die meist durch das Programm selbst (Konvertierungsprobleme, Typisierungsfehler etc.) oder unvorhergesehene Änderungen der Ausführungsbedingungen (Netzwerkabriss, Dateisystemfehler, etc.) ausgelöst werden können und gesichert abgefangen werden müssen. Hierzu dienen try-catch-Blöcke.
Der folgende Codeausschnitt zeigt den Einsatz des try-catch-Blockes am Beispiel des Aufrufs des Default-Browsers, mit dem hier die Webseite von Javabeginners dargestellt wird.
try { Desktop.getDesktop().browse(new URI("https://javabeginners.de")); } catch (IOException ioe) { System.out.println("IO-Problem"); ioe.printStackTrace(); } catch (URISyntaxException use) { System.out.println("URI-Problem"); use.printStackTrace(); } System.out.println("weiter geht's...");
Innerhalb des try
-Blockes wird die gewünschte
Anweisung formuliert, die ggf. zu Fehlern führen kann. Bei
der Ausführung des obigen Codes besteht die
Möglichkeit, dass im Wesentlichen zweierlei Fehler
auftreten: Beim Netzwerkverkehr könnten Probleme bestehen
oder die Auflösung des URI könnte fehlschlagen.
In
beiden Fällen wird jeweils eine spezielle Exception
ausgelöst ("geworfen"), die innerhalb eines eigenen catch
-Blockes
abgefangen werden muss.
Gelegentlich kommt es wie oben vor, dass nicht nur eine, sondern
mehrere Exceptions geworfen werden können. Deren Abfangen
führt zu einer unschönen und schlecht lesbaren Folge
von catch
-Anweisungen. Ab Java 7.0 kann dies durch
eine Multi-catch genannte Zusammenfassung der
Exceptiontypen vereinfacht werden, wie im folgenden
Code-Abschnitt gezeigt. Als Argument der catch
-Anweisung
werden hierbei die abzufangenden Exceptiontypen durch ein
Pipe-Zeichen '|' getrennt.
try { Desktop.getDesktop().browse(new URI("https://javabeginners.de")); } catch (IOException | URISyntaxException e) { e.printStackTrace(); System.out.println("IO- oder URI-Problem"); } System.out.println("weiter geht's...");
Wie die Ausgabeanweisungen hier zeigen, können innerhalb
des catch
-Blockes beliebige Maßnahmen
ergriffen werden, die im Falle einer Exception durchgeführt
werden sollen.
Zur Demonstration der Auslösung einer
Exception kann z.B. die Protokollangabe des URL fehlerhaft
verändert werden, etwa von https://
zu htp://
.
Die Folge ist eine IOException
, die verhindert,
dass der Browser geöffnet wird. Gleichzeitig wird die
folgende Ausgabe generiert:
java.io.IOException: Failed to mail or browse htp://javabeginners.de. Error code: -10814 at sun.lwawt.macosx.CDesktopPeer.lsOpen(CDesktopPeer.java:72) at sun.lwawt.macosx.CDesktopPeer.browse(CDesktopPeer.java:65) at java.awt.Desktop.browse(Desktop.java:386) at MultiCatch.main(MultiCatch.java:12) IO-Problem weiter geht's...
Man erkennt hier an der Ausgabe von "weiter geht's...", dass das
Programm nach dem Abfangen der Ausnahme hinter dem
try-catch-Block weiter ausgeführt wird. Das Programm wird
also nicht beendet.
Diese Tatsache verleitet oft dazu, auf
offensichtlich notwendige Sicherungsabfragen im Code zu
verzichten und statt dessen einfach eine try-catch-Anweisung zu
verwenden. Schauen wir uns den folgenden Code an:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); int i = 0; try { i = new Integer(reader.readLine()); } catch (IOException e) { e.printStackTrace(); }
Hier wird eine Eingabe von der Konsole gelesen, als int
gespeichert und ausgegeben. Das geht so lange gut, wie auch ein
numerischer Wert eingegeben wird. Beim Auslesen von
nicht-numerischen Werten wird hingegen eine java.util.NumberFormatException
ausgelöst, da ein solches Literal nicht in einen
numerischen Wert gewandelt werden kann. Ginge man nun her und
würde diesen Fall lediglich durch einen try-catch-Block
absichern, so wären zwar Fehler abgesichert, ein sinnvoller
Programmablauf selbst wäre jedoch nicht unbedingt gegeben,
da die Applikation ohne Behandlung der Eingabe weiterlaufen
würde. Die falsche Belegung der Variablen könnte im
weiteren Verlauf u.U. zu schwerwiegenden Fehlfunktionen
führen.
Im gezeigten Fall sollte somit unbedingt eine Prüfung des Eingabetyps vorangehen und eine Fehleingabe möglichst korrigiert werden können. Dies kann, je nach Einzelfall, auf unterschiedliche Weise erfolgen, etwa durch entsprechende Verwendung eines Scanner-Objektes oder auch durch eine Prüfung über reguläre Ausdrücke. Sie wird hier jedoch der Übersichtlichkeit halber in der Folge nicht berücksichtigt.
Das Beispiel demonstriert jedoch auch, dass Fehler die beim
Einlesen der Eingabe auftreten können, über eine IOException
abgefangen werden müssen. Wird nicht wie hier vom
Standard-Eingabestrom gelesen, sondern z.B. aus einer Datei, so
muss der Strom nach seiner Nutzung wieder geschlossen werden.
(Der Standard-Eingabestrom ist und bleibt immer offen.)
BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("test.txt")); int i = new Integer(reader.readLine()); reader.close(); } catch (IOException e) { e.printStackTrace(); }
Dies geschieht hier innerhalb des try
-Blockes und
ist aus diesem Grunde nicht unproblematisch, da er im Fehlerfall
nicht vollständig ausgeführt und somit der Stream u.U.
auch nicht geschlossen wird.
Hier kann ein dritter Block an
das try-catch-Statement angeschlossen werden, der mit dem
Schlüsselwort finally
eingeleitet wird:
BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("test.txt")); int i = new Integer(reader.readLine()); } catch (IOException e) { e.printStackTrace(); } finally { try { reader.close(); } catch (IOException e) {} }
Dieser optionale Abschnitt wird immer und auf jeden Fall
ausgeführt, auch wenn die try
-Anweisung
fehlschlägt, etwa weil in deren Verlauf ein Fehler
auftritt. Hier ist der geeignete Ort, um Ressourcen freizugeben,
Dateien zu schließen, die u.U. bei der Ausführung des
try
-Blockes geöffnet wurden oder wie hier Streams wieder zu
schließen. Da die Methode close()
ihrerseits
eine IOException
auslösen kann, muss sie
ebenfalls in einen gesonderten try-catch-Block eingeschlossen
werden, leider eine ziemlich unübersichtliche Konstruktion,
die jedoch seit Java 7.0 durch das try-with-resources
Statement zum Glück deutlich vereinfacht
werden kann.
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.