Advanced Java Services | try catch finally |
Bei den checked exceptions erzwingt der Compiler eine Fehlerbehandlung. Ohne eine Fehlerbehandlung in irgendeiner Form können wir z.Bsp. keine Datei öffnen. Liest man die Fehlermeldungen von checked exceptions unreported exception XxxException must be caught or declared to be thrown, so gibt es anscheinend zwei Möglichkeiten der Fehlerbehandlung. Man kann den Fehler entweder abfangen (must be caught) oder weiterwerfen wie eine heiße Kartoffel (declared to be thrown). Im letzteren Fall muß dann eine übergeordnete Instanz den Fehler behandeln. Wir behandeln zunächst den ersten Fall und fangen den Fehler direkt ab. Dies geschieht mit einem try-catch Konstrukt oder in erweiterter Form mit einem try-catch-finally Konstrukt.
try // Versuchsblock { // statements vor dem kritischen statement Thread.sleep(2000); // statements nach dem kritischen statement } catch(InterruptedException ex) // Fehlerbehandlungsblock { System.out.println("Das ist nicht gut gegangen"); } //nächste Anweisung
Die Klammern in den beiden Blöcken sind obligatorisch. Der Versuchsblock wird bis zum kritischen Statement abgearbeitet. Erzeugt das kritische Statement keinen Fehler, so wird es abgearbeitet und die nächsten Statements werden abgearbeitet bis zu einem weiteren kritischen Statement oder bis zum Ende des Versuchsblocks. In diesem Fall wird der catch-Block übersprungen und es folgt die nächste Anweisung nach dem catch-Block. Scheitert ein kritische Aufruf dagegen, so werden die Statements nach dem kritischen Aufruf nicht mehr abgearbeitet, sondern sofort der catch-Block angesprungen und seine Anweisungen abgearbeitet.
Zu einem try-Block kann es durchaus mehrere catch-Blöcke geben. Es kommt lediglich darauf an wie ausführlich man die Fehlerbehandlung durchführen will. Nehmen wir an, wir lesen Strings aus einer ASCII-Datei. Die Strings sind Ziffernfolgen, die wir im Programm etwa als Bildschirmkoordinaten brauchen. Wir müssen also die Strings in int-Zahlen umwandeln. Das könnte so aussehen
int zahl; try { BufferedReader br = new BufferedReader( new FileReader("foo.dat") ); // FileNotFoundException (checked) String line = br.readLine(); // IOException (checked) zahl = Integer.parseInt(line); // NumberFormatException (unchecked) } catch(FileNotFoundException ex) { System.out.println("Datei wurde nicht gefunden"); } catch(IOException ex) { System.out.println("Datei kann nicht gelesen werden"); } catch(NumberFormatException ex) { System.out.println("String kann nicht in Zahl umgewandelt werden"); }
Alle drei Statements werfen verschiedene Exceptions und für jede Exception haben wir einen eigenen catch-Block. So kann man auf jeden Fehler individuell reagieren. Will man keine so ausführliche Fehlerbehandlung, so nimmt man die erste gemeinsame Oberklasse der drei Exceptions und dann reicht ein einziger catch-Block. Da NumberFormatException eine spezielle RuntimeException ist, ist die erste gemeinsame Oberklasse Exception selbst.
int zahl; try { BufferedReader br = new BufferedReader( new FileReader("foo.dat") ); // FileNotFoundException (checked) String line = br.readLine(); // IOException (checked) zahl = Integer.parseInt(line); // NumberFormatException (unchecked) } catch(Exception ex) { System.out.println("Es ist was schiefgegangen"); }
Die Reihenfolge bei mehreren catch-Blöcken ist nicht immer beliebig. Stehen die Exceptions in einer direkten Vererbungslinie, so muß zuerst die spezielle Ausnahme und dann die allgemeinere Ausnahme abgefangen werden. Dies wird aber vom Compiler sofort beanstandet. Im letzten Beispiel ist FileNotFoundException eine Unterklasse von IOException. Die folgende Reihenfolge ist also falsch:
catch(IOException ex) { } catch(FileNotFoundException ex) { } //exception java.io.FileNotFoundException has already been caught
Der Compiler beanstandet den zweiten catch-Block, weil mit dem allgemeineren Fehler bereits der Spezialfall mitabgefangen wurde. Bei Ausnahmen, die nicht in Vererbung zueinander stehen ist dagegen die Reihenfolge egal. So ist etwa die folgende Reihenfolge der catch-Blöcke zulässig:
catch(FileNotFoundException ex) { } catch(NumberFormatException ex) { } catch(IOException ex) { }
Man kann das try-catch Konstrukt durch einen finally-Block ergänzen. Falls dieser optionale Block vorhanden ist, so wird er auf jeden Fall abgearbeitet, d.h. er kommt entweder direkt nach try oder nach dem letzten catch. Der finally-Block wird in der Regel dazu verwendet um irgendwelche Aufräumarbeiten zu erledigen.
try { // Versuchsblock } catch(InterruptedException ex) { // Fehlerbehandlungsblock } finally { // Aufräumblock } //nächste Anweisung
Laufzeitfehler wie NullPointerException und ArrayIndexOutOfBoundsException deuten in der Regel auf Programmfehler hin. Sie sollten daher nicht abgefangen werden, sondern die Fehler im Programm beseitigt werden.