Axel Rogat
Objektorientiertes Programmieren mit C++ und JAVA
 
23.6: Verschachtelte Klassen Kapitel 24 25: Vererbung in JAVA 
 
  24 Ausnahmebehandlung in JAVA  
 

Das Exception-Handling entspricht auf der technischen Seite im wesentlichen dem von C++. Die try/throw/catch-Syntax ist übernommen worden. Bei Auftreten einer Exception wird genau wie in C++ der Runtime-Stack aufgeräumt, und ggf. werden Objekte freigegeben, bis ein passendes catch-Statement gefunden wird.

In throw-Statements dürfen nur Objekte der Klasse Throwable geworfen werden, insbesondere also keine einfachen Typen. Die beiden Klassen Exception und Error, JAVA-Klasse Error sind Unterklassen von Throwable. Man hat es aber fast immer nur mit Exception-Objekten zu tun, da sich Errors meist auf Compiler- und Interpreter-Interna beziehen und üblicherweise nicht abgefangen zu werden brauchen.

Eine Vielzahl von speziellen Ausnahmen wird von Exception abgeleitet, und eigene Exceptions sollten natürlich auch immer Unterklasse dieser Klasse sein.

Objekte, die geworfen werden sollen, werden in JAVA meistens mit einem new im throw-Statement erzeugt:

try { throw new Exception("Martians have landed."); } catch (Exception e) { System.out.println(e); }
Hier wird der (String)-Konstruktor von Exception verwendet (der natürlich von allen Unterklassen geerbt wird).

Die Methode toString() gibt den Namen der (Unter-)Klasse und diesen Text aus. Unterklassen dürfen natürlich toString()-Methoden definieren, die genauere Angaben machen. IndexOutOfBoundsException baut zum Beispiel zusätzlich den schuldigen Index in den Text ein.

Beispielsweise entwerfen wir eine Exception, die ausgelöst wird, wenn in einer Scanner-Routine für irgendeine Eingabe ein ungültiges Zeichen gelesen wird:

class IllegalCharacterException extends Exception { char c; public IllegalCharacterException(char thechar) { c=thechar; } public String toString() { return "Illegal character '"+c+"' (code "+(int)c+") encountered."; } }
Zusammen mit der großen Zahl von Standard-Klassen stellt JAVA auch eine große Zahl von Standard-Exceptions bereit. Sie werden unterteilt in "checked" und "unchecked" Exceptions:

Checked
sind solche, die "früher" (etwa in C) durch Abfragen des Rückgabewerts einer Systemfunktion erledigt wurden, etwa beim Belegen von Speicher oder beim Öffnen einer Datei. Solche Situationen sind meist gut handhabbar.

Unchecked
sind Exceptions, die an sehr vielen Stellen im Programm auftauchen können, etwa schwere Arithmetik-Fehler, unzulässige Indizes, etc. Dabei handelt es sich oft auch Programmierfehler, die nicht gut behebbar sind.

throw-Deklarationen werden strenger gehandhabt als in C++. Checked exceptions müssen vom Programmierer gehandhabt werden. Jede Methode muß in einer throws-Klausel (Achtung: umbenannt gegenüber C++) angeben, welche (checked) Exceptions sie möglicherweise nicht abfängt und also nach außen weitergibt. Wenn eine Methode aufgerufen wird, die angibt, eine bestimmte Exception weiterzugeben, dann muß die aufrufende Routine entweder diese Exception abfangen oder selbst in einem throws deklarieren:

void mymethod() throws FileNotFoundException { FileReader fr=new FileReader("myfile.txt"); // ... }
Da der Konstruktor von java.io.FileReader die Exception FileNotFoundException wirft und diese innerhalb der Routine nicht abgefangen wird, muß sie nach dem Funktionskopf angegeben werden. (Mehrere Exceptions werden dort mit Kommas voneinander getrennt.)

Unchecked Exceptions sind im wesentlichen in der Unterklasse RuntimeException zusammengefaßt. Sie brauchen also nicht abgefangen oder deklariert zu werden. Sie werden an das Laufzeitsystem weitergegeben und führen zu einem Fehlerabbruch des Programms.

Durch den Zwang zu throws-Deklarationen kann es der Compiler erkennen, wenn eine bestimmte Exception in einem try-Block nicht geworfen werden kann. Wird dennoch ein entsprechendes catch-Statement angegeben, gibt er einen Fehler aus.
try { // ... } catch (FileNotFoundException e) { // ... } catch (IOException e) { // ... } catch (Exception e) { System.out.println(e); }
  Es gibt kein catch(...) für beliebige Ausnahmen wie in C++, was wegen der Hierarchie der Exception-Klassen aber auch nicht erforderlich ist. Wenn man beliebige Exceptions abfangen möchte, gibt man im catch-Statement einfach die allgemeinste Klasse an (siehe links).

Man kann im catch-Block auch nicht mehr wie in C++ mit einem throw; die Exception erneut werfen; das Exception-Objekt muß explizit neu angegeben werden (z.B. throw e;).

try { // ... } catch (SomeException e) { // ... } catch (SomeOtherException e) { // ... } finally { // Code für alle 3 Fälle }
  Dafür gibt es den neuen finally-Block, der irgendwo nach dem try-Block und der Position direkt hinter dem letzten catch-Block stehen muß -- die genaue Position vor, zwischen oder hinter den catches ist gleichgültig.

Der finally-Code wird auf jeden Fall ausgeführt, entweder, wenn der try-Block korrekt beendet wurde (auch, wenn er mit return verlassen wird!!), oder nach Ausführung eines der catch-Fälle.

Das macht deshalb Sinn, weil im Fehlerfall oft einiges an Aufräumarbeit notwendig ist, der eventuell in allen catch-Fällen derselbe wäre und dupliziert werden müßte.

Das folgende Bild gibt eine Übersicht über die Exception-Hierarchie (aus Formatgründen von links nach rechts):

 
23.6: Verschachtelte Klassen Startseite 25: Vererbung in JAVA 
 

© 1998 Axel Rogat