try, catch und finally
Kurz gesagt: Mit mehreren catch-Blöcken fängst du verschiedene Arten von Exceptions, vom Speziellen zum Allgemeinen, und finally läuft immer, mit oder ohne Fehler.
Problem
Die Rechnung für null Personen
Das Programm teilt die Restaurantrechnung gleichmäßig auf. Falsche Eingaben kennst du schon, und du fängst sie:
import java.util.Scanner;
public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("Pro Person: " + bill / people + " €"); } catch (NumberFormatException e) { System.out.println("Gib ganze Zahlen ein."); } }}Die Rechnung beträgt 90 €, und jemand gibt 0 als Zahl der Personen ein. Das Programm hält wieder an:
Exception in thread "main" java.lang.ArithmeticException: / by zero at Main.main(Main.java:9)catch ist da, hilft aber nicht. Es fängt nur NumberFormatException, und das hier ist eine andere Art von Exception.
Die Idee
Mehrere catch-Blöcke
Auf ein try können mehrere catch-Blöcke folgen, einer für jede Art von Fehler:
Scanner in = new Scanner(System.in);try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("Pro Person: " + bill / people + " €");} catch (NumberFormatException e) { System.out.println("Gib ganze Zahlen ein.");} catch (ArithmeticException e) { System.out.println("Es können nicht 0 Personen sein.");}| Eingabe | Ausgabe |
|---|---|
| 90 und 3 | Pro Person: 30 € |
| 90 und 0 | Es können nicht 0 Personen sein. |
| 90 und drei | Gib ganze Zahlen ein. |
Die Blöcke werden von oben nach unten geprüft. Der erste, dessen Art passt, läuft, und nur dieser. Die übrigen werden übersprungen.
Scanner in = new Scanner(System.in);try { int bill = Integer.parseInt(in.nextLine()); int people = Integer.parseInt(in.nextLine()); System.out.println("Pro Person: " + bill / people + " €");} catch (NumberFormatException e) { System.out.println("Gib ganze Zahlen ein.");} catch (ArithmeticException e) { System.out.println("Es können nicht 0 Personen sein.");}Schritt für Schritt, in Worten
- Erster Lauf: Die Rechnung beträgt 90 €, und es sind 0 Personen.
- 90 / 0 ist eine Division durch null: ArithmeticException wird geworfen.
- Das erste catch ist für NumberFormatException. Es passt nicht und wird übersprungen.
- Das zweite passt. Nur dieses läuft.
- Zweiter Lauf: Statt der Zahl der Personen wird „drei“ eingegeben.
- parseInt kann keine Zahl machen: NumberFormatException wird geworfen.
- Schon das erste catch passt. Das zweite wird nie erreicht.
- Die Blöcke werden von oben nach unten geprüft. Der erste, der passt, läuft.
Genauer, für Neugierige
- Wenn zwei Arten gleich behandelt werden, kannst du sie in einem Block mit
|fangen:catch (NumberFormatException | ArithmeticException e).
Die Idee
Die Familie der Exceptions
Exceptions sind Klassen, die durch Vererbung verbunden sind. Hier sind die häufigsten:
Throwable├── Error ernstes Problem der JVM, wird nicht gefangen│ ├── OutOfMemoryError│ └── StackOverflowError└── Exception ├── IOException Dateien, Netzwerk ├── InterruptedException └── RuntimeException ├── ArithmeticException ├── IllegalArgumentException │ └── NumberFormatException ├── IndexOutOfBoundsException │ └── ArrayIndexOutOfBoundsException └── NullPointerExceptioncatch fängt die Art, die du angibst, und alle ihre Unterklassen. Das ist die Polymorphie aus dem Modul über Vererbung: Eine Variable vom Typ der Basisklasse kann auf ein Objekt einer Unterklasse zeigen. Deshalb fängt catch (Exception e) alles:
static void split(String billText, String peopleText) { try { int bill = Integer.parseInt(billText); int people = Integer.parseInt(peopleText); System.out.println("Pro Person: " + bill / people + " €"); } catch (Exception e) { System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage()); }}
split("90", "3");split("90", "0");split("90", "drei");Pro Person: 30 €ArithmeticException: / by zeroNumberFormatException: For input string: "drei"e.getClass().getSimpleName() gibt den Namen der echten Klasse des Objekts. Die Variable ist vom Typ Exception, aber das Objekt ist von einer Unterklasse.
Genauer, für Neugierige
Errorund seine Unterklassen sind Probleme der JVM selbst, etwa wenn der Speicher ausgeht oder der Stapel überläuft. Die JVM wirft sie selbst. Danach kann das Programm meistens nichts Sinnvolles mehr tun, deshalb fängt man sie nicht.catch (Throwable t)würde auch sie fangen. Tu das nicht.
- Fange so speziell, wie du kannst. catch (Exception e) versteckt auch Fehler, die du nicht erwartet hast, etwa einen Bug in deinem eigenen Code.
Probier es aus
Welches catch?
Was gibt das Programm aus?
int[] seats = { 4, 2, 6 };try { System.out.println("Tisch 4: " + seats[3] + " Plätze");} catch (ArithmeticException e) { System.out.println("Division durch null");} catch (RuntimeException e) { System.out.println("Laufzeitfehler");} catch (Exception e) { System.out.println("Allgemeiner Fehler");}seats[3] liegt außerhalb des Arrays mit 3 Elementen. ArrayIndexOutOfBoundsException ist keine ArithmeticException, deshalb wird der erste Block übersprungen. Sie ist eine Unterklasse von RuntimeException, deshalb passt der zweite und läuft. Der dritte wird nie erreicht, obwohl er auch passen würde.Probier es aus
Finde den Fehler
Wähle die Zeile mit dem Fehler.
Exception ist die Basisklasse von ArithmeticException. Das erste catch fängt alles, und das zweite kann nie erreicht werden. Der Compiler sagt es: „exception ArithmeticException has already been caught“. Die Regel lautet vom Speziellen zum Allgemeinen: Vertausche die beiden Blöcke.Die Idee
Checked Exceptions und throws
Bei manchen Exceptions zwingt dich der Compiler, sie zu behandeln. Hier ist eine Methode, die einen Kassenbon druckt und mit Thread.sleep (eine Pause in Millisekunden) auf den Drucker wartet:
static void printReceipt() { System.out.println("Drucke den Kassenbon..."); Thread.sleep(300); System.out.println("Fertig.");}Das Programm lässt sich nicht kompilieren: „unreported exception InterruptedException; must be caught or declared to be thrown“. Die Pause kann von außen unterbrochen werden, und der Compiler will, dass du sagst, was dann passieren soll. Du hast zwei Möglichkeiten: Du fängst die Exception in der Methode, oder du deklarierst sie mit throws und lässt den Aufrufer entscheiden:
static void printReceipt() throws InterruptedException { System.out.println("Drucke den Kassenbon..."); Thread.sleep(300); System.out.println("Fertig.");}
try { printReceipt();} catch (InterruptedException e) { System.out.println("Unterbrochen.");}Drucke den Kassenbon...Fertig.- Checked Exceptions (geprüfte) sind die Unterklassen von
Exception, die keineRuntimeExceptionsind. Sie kommen meistens von außen, von Dingen außerhalb des Programms: eine fehlende Datei, eine abgebrochene Netzwerkverbindung, eine unterbrochene Pause. - Unchecked Exceptions (ungeprüfte) sind
RuntimeExceptionund ihre Unterklassen. Sie sind meistens ein Fehler im Code oder in der Eingabe: ein Index, eine Division durch null, ein falsches Format. Der Compiler zwingt dich nicht, sie zu fangen.
Am häufigsten triffst du eine Checked Exception bei Dateien: IOException.
Die Idee
finally: immer
Der Drucker an der Kasse muss nach jedem Kassenbon freigegeben werden, sonst wird der nächste nicht gedruckt. Egal, was passiert, Erfolg oder Fehler:
static void printReceipt(String qtyText) { System.out.println("Der Drucker ist belegt."); try { int qty = Integer.parseInt(qtyText); System.out.println("Kaffee: " + qty + " x 2 € = " + qty * 2 + " €"); } catch (NumberFormatException e) { System.out.println("Falsche Menge: " + qtyText); } finally { System.out.println("Der Drucker ist frei."); }}
printReceipt("3");printReceipt("drei");Der Drucker ist belegt.Kaffee: 3 x 2 € = 6 €Der Drucker ist frei.Der Drucker ist belegt.Falsche Menge: dreiDer Drucker ist frei.finally läuft immer: wenn alles gut geht, wenn die Exception gefangen wird, und sogar wenn keiner dieser catch-Blöcke sie fängt. Im letzten Fall läuft finally, und die Exception geht weiter nach oben zum nächsten try, das sie fangen kann.
Warum nicht einfach eine Zeile nach dem try/catch? Weil diese Zeile übersprungen wird, wenn die Exception von einer Art ist, die catch nicht fängt, oder wenn im try ein return steht. finally wird nicht übersprungen.
Genauer, für Neugierige
- Es geht auch
trymitfinallyund ohnecatch: Die Exception wird nicht gefangen, aber das Aufräumen läuft.
- Für Ressourcen, die geschlossen werden müssen, etwa Dateien und Datenbankverbindungen, gibt es eine kürzere Form:
try (...)mit der Ressource in den Klammern (try-with-resources). Wir sehen sie, wenn wir zu den Dateien kommen. Diese Form schließt die Ressource von selbst, so wiefinallyes tun würde.
finallyläuft nur dann nicht, wenn das ganze Programm angehalten wird, etwa mitSystem.exit(0).
Probier es aus
finally und return
Was gibt das Programm aus?
static String reserve(int guests) { try { if (guests > 6) { return "Kein Tisch für " + guests; } return "Tisch für " + guests; } finally { System.out.println("Versuch protokolliert."); }}
System.out.println(reserve(8));Ein return im try überspringt finally nicht. Die Methode führt zuerst finally aus und gibt dann den Text zurück. Deshalb wird „Versuch protokolliert.“ vor dem Ergebnis ausgegeben, das println erst bekommt, wenn die Methode fertig ist.
Übung
Programme mit mehreren catch-Blöcken
Löse die Aufgaben in deinem Editor. Gib keine Eingabeaufforderungen aus.
1. Preis pro Person. Die Preise stehen im Array { 120, 75, 300 }. Lies eine Produktnummer (ab 1) und die Zahl der Personen und gib den Preis pro Person mit Ganzzahldivision aus. Fange drei Dinge: ein Produkt, das es nicht gibt, null Personen und alles andere (dann gib die Meldung der Exception aus).
| Eingabe | Ausgabe |
|---|---|
| 2 und 3 | Pro Person: 25 € |
| 5 und 2 | Dieses Produkt gibt es nicht. |
| 1 und 0 | Die Zahl der Personen darf nicht 0 sein. |
| 1 und zwei | Fehler: For input string: "zwei" |
2. Taschenrechner. Lies eine ganze Zahl, einen Operator (+, -, * oder /) und eine zweite ganze Zahl, jede in einer eigenen Zeile. Gib die Rechnung aus. Bei Text statt einer Zahl sag „Gib ganze Zahlen ein.“, bei Division durch null „Division durch null.“ und bei jedem anderen Operator „Unbekannter Operator: “ und dahinter den Operator.
84/484 / 4 = 2184/0Division durch null.Weitere Aufgaben
- Das Kartenterminal. Die Methode
pay(String amountText)gibt „Das Terminal ist verbunden.“ aus, dann „Bezahlt: X €“ oder eine Meldung über die falsche Eingabe, und imfinally„Das Terminal ist aus.“ Rufe sie mit „25“ und mit „zwanzig“ auf. - Durchschnittsnote ohne Absturz. Lies 5 Zeilen mit Noten. Überspringe die, die keine ganzen Zahlen sind (eine Exception) oder nicht zwischen 2 und 6 liegen (ein
if). Gib den Durchschnitt mit Ganzzahldivision aus. Wenn keine Note gültig ist, wirft die Division durch null: Fange sie und gib „Keine gültigen Noten.“ aus. - Vorhersagen und prüfen. Schreibe ein
trymitcatchundfinally, in dem jeder Block eine Zeile ausgibt. Sage die Ausgabe bei Erfolg, bei einer gefangenen und bei einer nicht gefangenen Exception voraus, und führe es dann aus.
- Pause. Schreibe eine Methode
pause(int ms), dieThread.sleep(ms)aufruft. Mach es einmal mitthrows InterruptedExceptionund einmal mittry/catchdarin. Was mussmainin jedem der beiden Fälle tun?
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Instanzmethoden · Vererbung · Polymorphie · Exceptions
- Geht weiter in: Werfen und eigene Exceptions · Dateien · Daten in Dateien · Hashcodes und Hashfunktionen
- Aufgaben zum Thema: Fehler und Exceptions