Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 13 - Exceptions |
Das Behandeln von Ausnahmen erfolgt mit Hilfe der try-catch-Anweisung:
001 try { 002 Anweisung; 003 ... 004 } catch (Ausnahmetyp x) { 005 Anweisung; 006 ... 007 } |
Der try-Block enthält dabei eine oder mehrere Anweisungen, bei deren Ausführung ein Fehler des Typs Ausnahmetyp auftreten kann. In diesem Fall wird die normale Programmausführung unterbrochen und der Programmablauf fährt mit der ersten Anweisung nach der catch-Klausel fort, die den passenden Ausnahmetyp deklariert hat. Hier kann nun Code untergebracht werden, der eine angemessene Reaktion auf den Fehler realisiert.
Wir wollen das folgende fehlerhafte Programm betrachten:
001 /* RTErrorProg1.java */ 002 003 public class RTErrorProg1 004 { 005 public static void main(String[] args) 006 { 007 int i, base = 0; 008 009 for (base = 10; base >= 2; --base) { 010 i = Integer.parseInt("40",base); 011 System.out.println("40 base "+base+" = "+i); 012 } 013 } 014 } |
RTErrorProg1.java |
Hier soll der String »40« aus verschiedenen Zahlensystemen
in ein int
konvertiert und als Dezimalzahl ausgegeben werden. Die dazu verwendete
Methode parseInt
überprüft, ob der übergebene String einen gültigen
Zahlenwert zur angegebenen Basis darstellt. Ist dies nicht der Fall,
löst sie eine Ausnahme des Typs NumberFormatException
aus. Ohne weitere Maßnahmen stürzt das Programm dann beim
Versuch, den String »40« als Zahl zur Basis 4 anzusehen,
ab:
40 base 10 = 40
40 base 9 = 36
40 base 8 = 32
40 base 7 = 28
40 base 6 = 24
40 base 5 = 20
Exception in thread "main" java.lang.NumberFormatException: 40
at java.lang.Integer.parseInt(Compiled Code)
at RTErrorProg1.main(Compiled Code)
Das Programm lässt sich nun leicht gegen solche Fehler absichern, indem die Programmsequenz, die den Fehler verursacht, in eine try-catch-Anweisung eingeschlossen wird:
001 /* Listing1303.java */ 002 003 public class Listing1303 004 { 005 public static void main(String[] args) 006 { 007 int i, base = 0; 008 009 try { 010 for (base = 10; base >= 2; --base) { 011 i = Integer.parseInt("40",base); 012 System.out.println("40 base "+base+" = "+i); 013 } 014 } catch (NumberFormatException e) { 015 System.out.println( 016 "40 ist keine Zahl zur Basis "+base 017 ); 018 } 019 } 020 } |
Listing1303.java |
Zwar ist 40 immer noch keine Zahl zur Basis 4, aber das Programm fängt
diesen Fehler nun ab und gibt eine geeignete Fehlermeldung aus:
40 base 10 = 40
40 base 9 = 36
40 base 8 = 32
40 base 7 = 28
40 base 6 = 24
40 base 5 = 20
40 ist keine Zahl zur Basis 4
In der catch-Klausel wird nicht nur die Art des abzufangenden Fehlers definiert, sondern auch ein formaler Parameter angegeben, der beim Auftreten der Ausnahme ein Fehlerobjekt übernehmen soll. Fehlerobjekte sind dabei Instanzen der Klasse Throwable oder einer ihrer Unterklassen. Sie werden vom Aufrufer der Ausnahme erzeugt und als Parameter an die catch-Klausel übergeben. Das Fehlerobjekt enthält Informationen über die Art des aufgetretenen Fehlers. So liefert beispielsweise die Methode getMessage einen Fehlertext (wenn dieser explizit gesetzt wurde) und printStackTrace druckt einen Auszug aus dem Laufzeit-Stack. Am einfachsten kann der Fehlertext mit der Methode toString der Klasse Throwable ausgegeben werden:
public String getMessage() public void printStackTrace() public String toString() |
java.lang.Throwable |
Unser Beispielprogramm könnte dann wie folgt umgeschrieben werden:
001 /* RTErrorProg2.java */ 002 003 public class RTErrorProg2 004 { 005 public static void main(String[] args) 006 { 007 int i, base = 0; 008 009 try { 010 for (base = 10; base >= 2; --base) { 011 i = Integer.parseInt("40",base); 012 System.out.println("40 base "+base+" = "+i); 013 } 014 } catch (NumberFormatException e) { 015 System.out.println("***Fehler aufgetreten***"); 016 System.out.println("Ursache: "+e.getMessage()); 017 e.printStackTrace(); 018 } 019 } 020 } |
RTErrorProg2.java |
Die Ausgabe des Programms wäre dann:
40 base 10 = 40
40 base 9 = 36
40 base 8 = 32
40 base 7 = 28
40 base 6 = 24
40 base 5 = 20
***Fehler aufgetreten***
Ursache: 40
java.lang.NumberFormatException: 40
at java.lang.Integer.parseInt(Compiled Code)
at RTErrorProg2.main(Compiled Code)
Wie man sieht, ähnelt die Ausgabe des Programms der ersten Version, die ohne expliziten Fehler-Handler geschrieben wurde. Das liegt daran, dass das Java-Laufzeitsystem beim Auftreten eines Fehlers, der von keiner Methode behandelt wurde, printStackTrace aufruft, bevor es das Programm beendet.
Alle Laufzeitfehler in Java sind Unterklassen der Klasse Throwable. Throwable ist eine allgemeine Fehlerklasse, die im Wesentlichen eine Klartext-Fehlermeldung speichern und einen Auszug des Laufzeit-Stacks ausgeben kann. Unterhalb von Throwable befinden sich zwei große Vererbungshierarchien:
Viele Pakete der Java-Klassenbibliothek definieren ihre eigenen Fehlerklassen. So gibt es spezielle Fehlerklassen für die Dateiein- und -ausgabe, die Netzwerkkommunikation oder den Zugriff auf Arrays. Wir werden diese speziellen Fehlerklassen immer dann erläutern, wenn eine Methode besprochen wird, die diese Art von Fehler erzeugt.
Die Reaktion auf eine Ausnahme muss keinesfalls zwangsläufig darin bestehen, das Programm zu beenden. Stattdessen kann auch versucht werden, den Fehler zu beheben oder zu umgehen, um dann mit dem Programm fortzufahren. Wird im obigen Programm die try-catch-Anweisung in die Schleife gesetzt, so fährt das Programm nach jedem Fehler fort und versucht, die Konvertierung zur nächsten Basis vorzunehmen:
001 /* Listing1305.java */ 002 003 public class Listing1305 004 { 005 public static void main(String[] args) 006 { 007 int i, base = 0; 008 009 for (base = 10; base >= 2; --base) { 010 try { 011 i = Integer.parseInt("40",base); 012 System.out.println("40 base "+base+" = "+i); 013 } catch (NumberFormatException e) { 014 System.out.println( 015 "40 ist keine Zahl zur Basis "+base 016 ); 017 } 018 } 019 } 020 } |
Listing1305.java |
Die Ausgabe des Programms lautet nun:
40 base 10 = 40
40 base 9 = 36
40 base 8 = 32
40 base 7 = 28
40 base 6 = 24
40 base 5 = 20
40 ist keine Zahl zur Basis 4
40 ist keine Zahl zur Basis 3
40 ist keine Zahl zur Basis 2
Ob es sinnvoller ist, nach einem Fehler mit der Programmausführung fortzufahren oder das Programm abzubrechen, ist von der Art des Fehlers und der Stelle im Programm, an der er aufgetreten ist, abhängig. Hier muss von Fall zu Fall entschieden werden, wie vorgegangen werden soll. |
|
Bisher sind wir davon ausgegangen, dass innerhalb eines try-Blocks nur eine Ausnahme auftreten kann. Tatsächlich ist es natürlich ohne Weiteres möglich, dass zwei oder mehrere unterschiedliche Ausnahmen ausgelöst werden. Das Programm kann auf verschiedene Fehler reagieren, indem es mehr als eine catch-Klausel verwendet. Jede catch-Klausel fängt die Fehler ab, die zum Typ des angegebenen Fehlerobjekts zuweisungskompatibel sind. Dazu gehören alle Fehler der angegebenen Klasse und all ihrer Unterklassen (das wichtige Konzept der Zuweisungskompatibilität von Objekttypen wurde in Abschnitt 8.1.6 erläutert). Die einzelnen catch-Klauseln werden in der Reihenfolge ihres Auftretens abgearbeitet.
Wir wollen das Beispielprogramm nun so erweitern, dass nicht nur ein einzelner String in eine Zahl konvertiert wird, sondern ein Array von Strings. Aufgrund eines Fehlers in der verwendeten for-Schleife verursacht das Programm einen Zugriff auf ein nicht vorhandenes Array-Element und löst damit eine Ausnahme des Typs IndexOutOfBoundsException aus. Diese kann zusammen mit der Ausnahme NumberFormatException in einer gemeinsamen try-catch-Anweisung behandelt werden:
001 /* Listing1306.java */ 002 003 public class Listing1306 004 { 005 public static void main(String[] args) 006 { 007 int i, j, base = 0; 008 String[] numbers = new String[3]; 009 010 numbers[0] = "10"; 011 numbers[1] = "20"; 012 numbers[2] = "30"; 013 try { 014 for (base = 10; base >= 2; --base) { 015 for (j = 0; j <= 3; ++j) { 016 i = Integer.parseInt(numbers[j],base); 017 System.out.println( 018 numbers[j]+" base "+base+" = "+i 019 ); 020 } 021 } 022 } catch (IndexOutOfBoundsException e1) { 023 System.out.println( 024 "***IndexOutOfBoundsException: " + e1.toString() 025 ); 026 } catch (NumberFormatException e2) { 027 System.out.println( 028 "***NumberFormatException: " + e2.toString() 029 ); 030 } 031 } 032 } |
Listing1306.java |
Die Ausgabe des Programms ist nun:
10 base 10 = 10
20 base 10 = 20
30 base 10 = 30
***IndexOutOfBoundsException: java.lang.ArrayIndexOutOfBoundsException
In der Praxis kommt es häufiger vor, dass in einem try-catch-Block auf mehrere Exceptions gleichartig reagiert wird. Entsprechend enthalten dann die catch-Blöcke identischen Code. Solche Redundanzen sind natürlich unschön. Ab Version 7 gibt es für dieses Problem eine Lösung, denn mehrere Exceptions, die nicht zuweisungskompatibel sind, können nun in einem einzigen catch-Block zusammen gefasst werden. Die Exception-Typen lassen sich dazu in der catch-Klausel mit dem Pipe-Symbol »|« verketten. |
|
Die beiden catch-Blöcke in Listing 13.6 können mit der Java Version 7 wie folgt zu einem zusammengefasst werden (Zeile 022 und Zeile 023):
001 /* Listing1307.java */ 002 003 public class Listing1307 004 { 005 public static void main(String[] args) 006 { 007 int i, j, base = 0; 008 String[] numbers = new String[3]; 009 010 numbers[0] = "10"; 011 numbers[1] = "20"; 012 numbers[2] = "30"; 013 try { 014 for (base = 10; base >= 2; --base) { 015 for (j = 0; j <= 3; ++j) { 016 i = Integer.parseInt(numbers[j],base); 017 System.out.println( 018 numbers[j]+" base "+base+" = "+i 019 ); 020 } 021 } 022 } catch (IndexOutOfBoundsException | 023 NumberFormatException x) { 024 System.out.println( 025 "***" + x.getClass().getSimpleName() + ": " + 026 x.toString() 027 ); 028 } 029 } 030 } |
Listing1307.java |
Zudem erkennt der Compiler ab Version 7 des JDK den richtigen Exception-Typ beim Weiterwurf von Ausnahmen. Das folgende Beispiel verdeutlicht dies. In dem try-catch-Block können potenziell zwei Exceptions auftreten, Exception_A und Exception_B. Beide werden in einem gemeinsamen catch-Block gefangen (Zeile 030) und weiter geworfen (Zeile 031). Der Compiler ab Version 7 erkennt, dass es sich nur um eine der beiden Exceptions handeln kann, und erlaubt es, diese in der throws-Klausel der main-Methode zu deklarieren (Zeile 023). |
|
001 /* Listing1308.java */ 002 003 class Exception_A extends Exception 004 { 005 // Typ A 006 } 007 008 class Exception_B extends Exception 009 { 010 // Typ B 011 } 012 013 public class Listing1308 014 { 015 void methode_A() throws Exception_A 016 { 017 } 018 019 void methode_B() throws Exception_B 020 { 021 } 022 023 public static void main(String[] args) throws Exception_A, Exception_B 024 { 025 Listing1308 l = new Listing1308(); 026 try { 027 l.methode_A(); 028 l.methode_B(); 029 } 030 catch (Exception x) { 031 throw x; 032 } 033 } 034 } |
Listing1308.java |
In einer JDK-6-kompatiblen Version des obigen Listings hätte die main-Methode in Zeile 023 den Typ Exception in der throws-Klausel deklarieren müssen.
Die try-catch-Anweisung enthält einen optionalen Bestandteil, der bisher noch nicht erläutert wurde. Mit Hilfe der finally-Klausel, die als letzter Bestandteil einer try-catch-Anweisung verwendet werden darf, kann ein Programmfragment definiert werden, das immer dann ausgeführt wird, wenn die zugehörige try-Klausel betreten wurde. Dabei spielt es keine Rolle, welches Ereignis dafür verantwortlich war, dass die try-Klausel verlassen wurde. Die finally-Klausel wird insbesondere dann ausgeführt, wenn der try-Block durch eine der folgenden Anweisungen verlassen wurde:
Die finally-Klausel ist also der ideale Ort, um Aufräumarbeiten durchzuführen. Hier können beispielsweise Dateien geschlossen oder Ressourcen freigegeben werden. |
|
Die folgende Variation unseres Beispielprogramms benutzt finally dazu, am Ende des Programms eine Meldung auszugeben:
001 /* Listing1309.java */ 002 003 public class Listing1309 004 { 005 public static void main(String[] args) 006 { 007 int i, base = 0; 008 009 try { 010 for (base = 10; base >= 2; --base) { 011 i = Integer.parseInt("40",base); 012 System.out.println("40 base "+base+" = "+i); 013 } 014 } catch (NumberFormatException e) { 015 System.out.println( 016 "40 ist keine Zahl zur Basis "+base 017 ); 018 } finally { 019 System.out.println( 020 "Sie haben ein einfaches Beispiel " + 021 "sehr glücklich gemacht." 022 ); 023 } 024 } 025 } |
Listing1309.java |
Die Ausgabe des Programms ist:
40 base 10 = 40
40 base 9 = 36
40 base 8 = 32
40 base 7 = 28
40 base 6 = 24
40 base 5 = 20
40 ist keine Zahl zur Basis 4
Sie haben ein einfaches Beispiel sehr glücklich gemacht.
Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0 |
<< | < | > | >> | API | © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de |