Liste P: Programmieren mit Java WS 2001/2002 Prof. Dr. V. Turau FH Wiesbaden Kapitel 6: Ausnahmen Folie 103 : Ausnahmesituationen Ein Programm ist robust, wenn es bei unvorhersehbaren Situationen ein deterministisches Verhalten zeigt und nicht einfach sang und klanglos abstürzt. Ein robustes Programm ist auf Ausnahmesituationen vorbereitet und kann nach deren Auftreten sinnvoll weiterarbeiten. Beispiele für Ausnahmesituationen sind: IO-Fehler Überlauf eines externen Speichermediums Inkonsistente Datenstrukturen Folie 104 : Behandlung von Ausnahmesituationen in C In vielen Programmiersprachen muss die Behandlung von Ausnahmesituationen in das Programm kodiert werden Beispiel: Kopieren einer Datei in C: #include <stdio.h> #define BUFSIZE 1024 int main(int argc, char * argv[]) { int f1, f2, n; char buf[BUFSIZE]; ... if ((f1 = open(argv[1], O_RDONLY, 0)) == -1) error("Datei %s nicht gefunden", argv[1]); if ((f2 = creat(argv[2])) == -1) error("Datei %s kann nicht angelegt werden", argv[2]); while ((n = read(f1, buf, BUFSIZE)) > 0) if (write(f2, buf, n) != n) error("Fehler beim Schreiben in Datei %s", argv[2]); ... Folie 105 : Exception Handling Konsequenzen: Der Algorithmus ist nicht mehr gut zu erkennen Fehlerprüfung und Algorithmus sind stark verschränkt Ziel der Ausnahmebehandlung (Exception Handling) Saubere Trennung zwischen Algorithmus und Fehlerbehandlung Sprachkonstrukte zur Fehlerbehandlung zur Verfügung stellen Fehlerbehandlung durch Anwender erzwingen Fehlerbehandlung durch Compiler überprüfbar machen Folie 106 : Exception Handling -- Das Prinzip Eine Ausnahme wird erzeugt (geworfen, thrown), wenn eine unerwartete Situation auftritt Die Erzeugung und das Werfen erfolgt aus dem Programmcode Die Reaktion auf eine geworfene Ausnahme nennt man Behandlung (Abfangen, Handling) Ausnahmen sind Instanzen von Ausnahmeklassen Der Compiler überprüft, ob alle Ausnahmen, welche auftreten können, auch behandelt werden Folie 107 : Exception Handling -- Vereinbarung Kann bei der Ausführung einer Methode oder eines Konstruktors eine Ausnahme geworfen werden, so muss dies bei der Vereinbarung der Methode / des Konstruktors angezeigt werden Zur Vereinbarung wird die throws-Klausel verwendet Beispiele: Klasse FileReader: public FileReader(String name) throws FileNotFoundException public int read() throws IOException Klasse URL: public URL(String spec) throws MalformedURLException Folie 108 : Exception Handling Kann eine Anweisung im Rumpf einer Methode oder eines Konstruktors eine Ausnahme erzeugen, so hat der Programmierer zwei Möglichkeiten: Die Methode oder der Konstruktor vereinbart die Ausnahme in ihrer throws-Klausel Die Methode oder der Konstruktor kann die Ausnahme abfangen (mit einer try-catch-Anweisung). In diesem Fall wird die Ausnahme nicht in die throws-Klausel aufgenommen Folie 109 : Exception Handling (Beispiel) Eine Methode soll das erste Zeichen einer Textdatei auslesen. Die folgende Implementierung kann nicht übersetzt werden: public int readErstesZeichen(String dateiName) { FileReader fr = new FileReader(dateiName); return fr.read(); } Fehlermeldungen: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown unreported exception java.io.IOException; must be caught or declared to be thrown Folie 110 : Exception Handling (Beispiel) Möglichkeit 1: public int readErstesZeichen(String dateiName) throws FileNotFoundException, IOException { FileReader fr = new FileReader(dateiName); return fr.read(); } Die folgende Verwendung der Methode ist nicht möglich: public static void main(String[] a) { char c = readErstesZeichen(a[0]); } Die Methode main muss die Ausnahmen von readErstesZeichen behandeln oder vereinbaren Folie 111 : Exception Handling (Beispiel) Möglichkeit 2: public int readErstesZeichen(String dateiName) { try { FileReader fr = new FileReader(dateiName); return fr.read(); } catch (FileNotFoundException fnfw) { System.err.println(dateiName + " nicht gefunden"); return -1; } catch (IOException ie) { System.err.println("Lesefehler: " + dateiName); return -1; } } Die folgende Verwendung der Methode ist möglich: public static void main(String[] a) { char c = readErstesZeichen(a[0]); } Folie 112 : try-catch-finally-Anweisung Ausnahmen werden durch try-catch-finally-Anweisungen behandelt: try { Anweisungen; } catch (Ausnahme1 a1) { Anweisungen; } catch (Ausnahme2 a2) { Anweisungen; } finally { Anweisungen; } Die finally-Anweisung ist optional Es können nur Ausnahmen abgefangen werden, die auch geworfen werden können Folie 113 : try-catch-finally-Anweisung Wird innerhalb des try-Blocks eine Ausnahme geworfen, dann werden die try-Blöcke von oben nach unten betrachtet ist die erzeugte Ausnahme Instanz der angegebenen Ausnahmeklasse, so werden die angegebenen Anweisungen ausgeführt die restlichen try-Blöcke werden übersprungen Ist eine finally-Anweisung vorhanden, so werden die Anweisungen ausgeführt Die finally-Anweisung wird auf jeden Fall ausgeführt, auch wenn keine Ausnahme auftrat Folie 114 : try-catch-finally-Anweisung Eine return-Anweisungen in einer finally-Anweisung überschreibt andere return-Anweisungen Beispiel: public int readZeichen() { try { return read(); } catch (Exception e) { return 0; } finally { return -1; } } Die Methode readZeichen gibt immer den Wert -1 zurück Folie 115 : Beispiel 1 Lesen einer Textdatei und Ausgabe des Inhalts auf der Standardausgabe try { FileReader fr = new FileReader(name); while ((zeichen = in.read()) != -1) System.out.println((char) zeichen); fr.close(); } catch (FileNotFoundException fnfw) { System.err.println(name + " nicht gefunden"); } catch (IOException ie) { System.err.println("Lesefehler: " + name); } Folie 116 : Beispiel 2 Falsche Reihenfolge der Ausnahmen (FileNotFoundException ist Unterklasse von IOException) try { FileReader fr = new FileReader(name); while ((zeichen = in.read()) != -1) System.out.println((char) zeichen); fr.close(); } catch (IOException ie) { System.err.println("Lesefehler: " + name); } catch (FileNotFoundException fnfw) { System.err.println(name + " nicht gefunden"); //Dieser Code wird nie erreicht! } Folie 117 : Beispiel 3 Rückgabetypen beachten! public int readErstesZeichen(String dateiName) { try { FileReader fr = new FileReader(dateiName); return fr.read(); } catch (FileNotFoundException fnfw) { System.err.println(dateiName + " nicht gefunden"); } catch (IOException ie) { System.err.println("Lesefehler: " + dateiName); return -1; } } Fehler: Der erste catch-Block enthält keine return-Anweisung! Folie 118 : Checked und Unchecked Exceptions Es gibt zwei Gruppen von Ausnahmen: checked und unchecked Exceptions Der Compiler überprüft nur die Behandlung von checked Exceptions Unterklassen der Klasse RuntimeException sollen nie behandelt werden. Hierzu zählen unter anderem: NullPointerException ArrayIndexOutOfBoundsException ArithmeticException Folie 119 : Klassenhierarchie Unterklassen von RuntimeException sind Unchecked Exceptions (NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException) Unterklassen von Exception (außer Unterklassen von RuntimeException) sind Checked Exceptions Folie 120 : Beispiel Negativbeispiel: Keine RuntimeExceptions abfangen try { File f = new File(a[0]); } catch (ArrayIndexOutOfBoundsException e){ .... } Negativbeispiel: Leere catch-Anweisung public static void main(String[] a) { try { File f = new File(a[0]); } catch (IOException e){} Folie 121 : Debugging von Ausnahmen Debugging von Ausnahmen: Die Methode printStackTrace try { File f = new File(a[0]); ..... } catch (Exception e){ e.printStackTrace(); } Ausgabe: java.io.FileNotFoundException: test.txt (Das System kann die angegebene Datei nicht finden) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(Unknown Source) at java.io.FileReader.<init>(Unknown Source) at IOTest.readErstesZeichen(IOTest.java:152) at IOTest.main(IOTest.java:7) Folie 122 : Eigene Ausnahmeklassen Benutzerdefinierte Ausnahmeklassen sollten checked exceptions sein Dazu sollten sie Unterklasse von Exception sein Beispiel: Bei der Methode pushString des Interfaces StringStack sollte verhindert werden, dass null in den Stack eingeführt wird public class StringStackException extends Exception { public StringStackException() { super("Illegales Stackelement: null"); } } Folie 123 : Verwendung Die Methode pushString vereinbart jetzt die Ausnahme StringStackException public void pushString(String s) throws StringStackException; Implementierung public void pushString(String s) throws StringStackException { if (s == null) throw new StringStackException(); ... } Achtung unterschiedliche Schlüsselwörter: throws und throw Folie 124 : Verwendung von Exceptions Exceptions sollten nicht zur Programmsteuerung verwendet werden try { while(true) { Object o = stack.pop(); ... } catch (EmptyStackException e) {} Die Methode main sollte alle auftretenden Ausnahmen behandeln. D.h. folgende Vereinbarung sollte in der Praxis nicht verwendet werden: public static void main(String[] a) throws Exception {