10.1 Java-Ausnahmen (Exceptions) behandeln

Java zwingt den Entwickler zur Behandlung von Ausnahmen wenn er Methoden aufruft die zu "Checked Exceptions" führen können.

Entwickler haben hier zwei Lösungsoptionen

  • die Möglichkeit die Behandlung einer Ausnahme an den Aufrufer der eigenen Methode zu delegieren
  • die Behandlung der Ausnahme im aktuellen Block selbst zu implementieren.

Der Javaübersetzer wird den quellcode nicht übersetzen falls der Entwickler keine Maßnahme ergriffen. Die zwangsweise Behandlung von "Checked Exceptions" ist Teil des Schnittstellenvertrags zur Benutzung einer Methode.

Behandeln einer Ausnahme mit einem try-catch Block

Java erlaubt die Ausnahmebehandlung mit Hilfe von try-catch-finally Programmblöcken die aus drei Teilen bestehen:

Block Anzahl Inhalt 
try einmal Block enthält den eigentlichen Anwendungscode. Er wird "versucht" (try) komplett auszuführen. In ihm können gewisse Ausnahmen auftreten. Er wird verlassen wenn wenn eine Ausnahme auftritt
catch einmal pro Exceptiontyp Block wird ausgeführt wenn eine bestimmte Ausnahme auftritt. Er "fängt" die Ausnahme und behandelt sie.
finally einmal, optional Block wird unbedingt ausgeführt. Er wird entweder nach dem regulären durchlaufen des try-Blocks oder nach dem Durchlaufen des catch-Blocks ausgeführt. Hier werden typischerweise Resourcen wie offene Dateien geschlossen und wieder freigegeben.

Der try Block wird beim Auftreten einer Ausnahme direkt verlassen und es wird in den catch-Block gesprungen.

Die Java hat die folgende Syntax:

try
{
  ... // in diesem Block wird der reguläre Code implementiert
      // Dieser Code kann an beliebigen Stellen Ausnahmen auslösen
}
catch (ExceptionTypeA myName1)
{
  ... // dieser Block wird aufgerufen wenn im try-Block eine Ausnahme
      // vom Typ ExceptionTypeA auslöst
}
catch (ExceptionTypeB myName2)
{
  ... // dieser Code wird aufgerufen wenn im try-Block eine Ausnahme
      // vom Typ ExceptionTypeB auslöst
}
finally
{
   ...// Dieser Block ist optional
       // er wird immer aufgerufen. Er hängt nicht vom Auslösen einer Ausnahme ab
}

Ablaufsteuerung

Wichtig: "Geworfene" Ausnahmen führen zum Beenden des Programmes falls sie nicht innerhalb eines try-Blocks mit der entsprechenden catch-Klausel und passendem Exception-typ gefangen und bearbeitet werden! Das Javaprogramm wird jede weitere Ausführung einstellen und die entsprechenden Ausführungsblöcke verlassen. Es wird auf der Kommandozeile das Auftreten der entsprechende Ausnahme dokumentieren.

Der try-Block kann jedoch in einem beliebigen äusseren Block implementiert sein. Das heißt, Methoden die einen solchen Codepfad direkt oder indirekt aufrufen, müssen in einem solchen try-catch-Block eingehüllt sein.

Ablauf mit finally Block Ablauf ohne finally Block
Aktivitätsdiagramm try-catch-finally Block Aktivitätsdiagramm try-catch Block;

 

Beispiel 1: NumberFormatException

Im folgenden Beispiel wird das erste Kommandozeilenargument in eine ganze Zahl umgewandelt. Hier kann eine NumberFormatException Ausnahme bei der Umwandlung der Eingabezeichenkette args[0] auftreten. Tritt diese Ausnahme auf, wird der Variablen firstArg eine Null zugewiesen.

package s1.block10;
public class Main1 {
    public static void main(String[] args) {
    int firstArg=0;
    int stellen=0;
    if (args.length > 0) {
        try {
            firstArg = Integer.parseInt(args[0]);
        } 
        catch (NumberFormatException e) {
            firstArg = 0;
        }
        finally {
           System.out.println("Die Zahl firstArg = " + firstArg + " wurde eingelesen");
        }
    }
}
}

In diesem Beispiel wurde die Größe des Eingabefeldes präventiv mit einer if Bedingung abgeprüft.

Beispiel 2: ArrayIndexOutOfBoundsException

Die präventive Prüfung der Feldgröße kann aber auch in einem zweiten catch Block zum gleichen try Block durchgeführt werden.

package s1.block10;
public class Main2 {

    public static void main(String[] args) {
        int firstArg = 0;
        int stellen = 0;
        try {
            firstArg = Integer.parseInt(args[0]);
        } catch (ArrayIndexOutOfBoundsException e) {
            firstArg = -1;
            System.out.println("Argument auf Position "
                    + e.getMessage()
                    + " nicht vorhanden");
        } catch (NumberFormatException e) {
            firstArg = 0;
        } finally {
            System.out.println("Die Zahl firstArg = " + firstArg + " wurde eingelesen");
        }
    }
}

Das Behandeln des Feldüberlaufs mit Hilfe eines catch-Blocks ist die flexiblere Lösung da sie jeglichen Feldüberlauf innerhalb des try-Blocks fangen kann.

Fehleranalyse im catch-Block

Dem catch-Block wird eine Instanz der bestimmten Klasse der Ausnahme mitgegeben. In Beispiel 2 wird die Ausnahmeinstanz e benutzt um mit Hilfe der Methode e.getMessage() die Postion im Feld auszulesen die die ArryIndexOutOfBoundsException Ausnahme auslöste.

Behandeln einer Ausnahme durch Delegation an die nächst äussere Methode

Die zweite Möglichkeit eines Entwicklers besteht darin, die Methode nicht direkt zu behandeln und das Behandeln der Ausnahme dem Aufrufer der akuellen Methode zu überlassen. Dies geschieht in dem man im Kopf der Methode dokumentiert welche Ausnahmen durch den Aufruf einer Methode ausgelöst werden können. Die Methode stringLesen2() kann zum Beispiel zum Auslösen einer IOException Ausnahme führen:

public static String StringLesen2() throws IOException{
...
}

Methoden können mehr als eine Ausnahme auslösen. Die Syntax des Methodenkopfs ist die folgende:

[Modifikatoren] Rückgabewert Name-der-Methode ([Parameter]) throws Name-Ausnahme [, Name-Ausnahme]

Der Aufrufer der Methode muss dann die Behandlung selbst durchführen.

Hinweis: Die Angabe der Ausnahmen die von einer Methode ausgelöst haben keine Bedeutung bei der Unterscheidung von überladenen Methoden.

Beispiel

Im folgenden Fall werden zwei Methoden zum Einlesen einer Konsoleneingabe gezeigt.

Die Methode stringLesen1() behandelt die IOException Ausnahme selbst. Der Aufrufer im main() Hauptprogramm muss keine Maßnahmen ergreifen.

Due Methode stringLesen2() behandelt die Ausnahme nicht und "exportiert" sie an den Aufrufer. Hier muss das Hauptprogramm main() die Ausnahme selbst.

package s1.block10;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main3 {

public static void main(String[] args) {
String eingabe = "";
eingabe = StringLesen1();
System.out.println(eingabe);
try {
eingabe = StringLesen2();
} catch (IOException e) {
eingabe = " KEINE EINGABE BEI ZWEITEM AUFRUF;";
}
System.out.println(eingabe);
}

public static String StringLesen1() {
String input = "Konsoleneingabe(1): ";
// liest einen vom Benutzer eingegebenen Text (String) ein
BufferedReader keyboard =
new BufferedReader(new InputStreamReader(System.in));
try {
input = input + keyboard.readLine() + ";";
} catch (IOException e) {
input = input + " KEINE EINGABE;";
}
return input;
}

public static String StringLesen2() throws IOException{
String input = "Konsoleneingabe(2): ";
// liest einen vom Benutzer eingegebenen Text (String) ein
BufferedReader keyboard =
new BufferedReader(new InputStreamReader(System.in));
input = input + keyboard.readLine() + ";";
return input;
}
}

 

Wichtig
Eine Methode kann nur "Checked Exceptions" auslösen die von ihr in der throws Klausel angegeben wurden.

Abarbeitung von mehreren catch-Klauseln

Java sucht bei mehreren catch-Klauseln (Handler) für eine Ausnahmebehandlung alle Klauseln von oben nach untern. Die erste Klausel mit einem passenden Typ der Ausnahme wird dann aufgerufen.

Aufgrund des Polymorphismus kann es mehrere Möglichkeiten geben. Klauseln die für eine Oberklasse einer Ausnahme geschrieben wurden werden benutzt werden wenn eine speziellere Ausnahme abgehandelt werden muss.

Beim Benutzen von mehreren catch Klauseln sollten daher die speziellen Ausnahmen vorne stehen und die allgemeinen Ausnahmen am Ende.

Finally Block (Vertiefung)

Der finally Block wird immer ausgeführt. Dies im Extremfall zu ungewöhnlichen Situationen führen. Analysieren Sie das folgende Programm:

package s1.block10;

class BeispielFinally {
public static int finallyTest (int a) {
int result=0;

try {
result = 17/a; // Hier kann eine ArithmeticException ausgelöst werden
}
catch (ArithmeticException e) {
return 0;
}
finally {
return 1;
}
//System.out.println("Methode beendet. Wir nie erreicht. Daher auskommentiert");
return result;
}
public static void main(String[] args) {
int x=0;
System.out.println(finallyTest(0));
}
}

Es erzeugt die folgende Konsolenausgabe:

1