„Hi, Amigo. Wir werden heute eine sehr interessante Lektion haben. Ich werde dir von Ausnahmen erzählen. Ausnahmen sind ein spezieller Mechanismus, mit dem wir Fehler im Programm behandeln können. Hier sind ein paar Beispiele für Fehler, die in einem Programm auftreten können:

1. Das Programm könnte versuchen, eine Datei zu schreiben, obwohl die Festplatte komplett voll ist.

2. Das Programm könnte versuchen, eine Methode für eine Variable aufzurufen, die eine Nullreferenz enthält.

3. Das Programm könnte versuchen, eine Zahl durch 0 zu teilen.“

Alle diese Aktionen führen zu Fehlern. In der Regel führt dies dazu, dass das Programm sofort beendet wird, da es in diesem Fall nicht sinnvoll wäre, den Code weiter auszuführen.

„Warum nicht?“

„Ist es sinnvoll, weiter am Lenkrad zu drehen, wenn das Auto von der Straße abgekommen ist und von einer Klippe fällt?“

„Muss das Programm dann abgebrochen werden?“

„Ja. Zumindest war das bisher so. Jeder Fehler führte zum Programmabbruch.“

„Das ist ziemlich schlau.“

„Aber wäre es nicht besser, zu versuchen, das Programm weiter auszuführen?“

„Ja. Angenommen, du hast eine große Menge Text in Word eingegeben und gespeichert. Was passiert, wenn der Speichervorgang fehlschlägt, aber das Programm dich glauben lässt, dass alles in Ordnung ist? Und du tippst weiter. Das wäre dumm, nicht wahr?“

„Ja.“

„Dann haben die Programmierer eine interessante Lösung gefunden: Jede Funktion sollte den Status ihrer Arbeit zurückgeben. 0 sollte bedeuten, dass sie wie erwartet funktioniert hat. Jeder andere Wert sollte bedeuten, dass irgendein Fehler aufgetreten ist; der Rückgabewert sollte ein Fehlercode sein.“

„Dieses Konzept hat aber auch seine Mängel. Nach jedem (!) Funktionsaufruf muss man den Rückgabecode (eine Zahl) überprüfen. Erstens ist das unpraktisch: Der Fehlerbehandlungscode wird selten ausgeführt, muss aber überall eingefügt werden. Zweitens geben Funktionen oft unterschiedliche Werte zurück – was soll ich dann damit machen?“

„Richtig. Darüber habe ich auch nachgedacht.“

„Dann kam eine glänzende Zukunft in Form von Ausnahmen und einem Fehlerbehandlungsmechanismus. So funktioniert es:

1. Wenn ein Fehler auftritt, erstellt die Java-Maschine ein spezielles Objekt, eine Ausnahme, in dem sie alle Fehlerinformationen speichert. Es gibt verschiedene Ausnahmen für verschiedene Fehler.

2. Eine Ausnahme bewirkt, dass das Programm die aktuelle Funktion und die nächste Funktion usw. sofort beendet, bis es die main-Methode beendet. Dann wird das Programm beendet. Programmierer sagen auch, dass die Java-Maschine den ‚Call Stack abwickelt‘.“

„Aber du hast gesagt, dass das Programm nicht immer abgebrochen wird.“

„Ja, weil es einen Weg gibt, eine Ausnahme abzufangen. Wir können speziellen Code an der richtigen Stelle schreiben, um die Ausnahmen, die uns wichtig sind, abzufangen und etwas mit ihnen zu machen. Das ist wirklich wichtig.“

„Dafür gibt es das spezielle try-catch-Konstrukt. Und so funktioniert es:“

Beispiel für ein Programm, das eine Ausnahme abfängt (Division durch 0) und weiterarbeitet.
public class ExceptionExample2
{
    public static void main(String[] args)
    {
        System.out.println("Program starts");

        try
        {
            System.out.println("Before calling method1");
            method1();
            System.out.println("After calling method1. This will never be shown");
        }
        catch (Exception e)
        {
           System.out.println("Exception has been caught");
        }

        System.out.println("Program is still running");
    }

    public static void method1()
    {
        int a = 100;
        int b = 0;
        System.out.println(a / b);
    }
}
Bildschirmausgabe:
Program starts
Before method1 calling
Exception has been caught
Program is still running

„Aber warum wird nicht ‚Nach dem Aufruf von methode1. Das wird niemals angezeigt‘ auf dem Bildschirm angezeigt?“

„Ich bin froh, dass du fragst. In Zeile 25 teilen wir durch 0, was zu einem Fehler – einer Ausnahme – führt. Die Java-Maschine erstellt ein ArithmeticException-Objekt mit Informationen über den Fehler. Das Objekt ist die Ausnahme.“

„Die Ausnahme tritt innerhalb der Methode method1 auf. Dadurch wird die Methode sofort beendet. Das würde dazu führen, dass die main-Methode beendet würde, wenn nicht der try-catch-Block wäre.“

„Wenn eine Ausnahme innerhalb eines try-Blocks auftritt, wird sie im catch-Block abgefangen. Der Rest des Codes im try-Block wird nicht ausgeführt. Stattdessen wird der catch-Block ausgeführt.

„Das verstehe ich nicht.“

„Mit anderen Worten, der Code funktioniert so:

1. Wenn eine Ausnahme innerhalb eines try-Blocks auftritt, stoppt der Code dort die Ausführung, wo die Ausnahme aufgetreten ist, und der catch-Block wird ausgeführt.

2. Wenn keine Ausnahme auftritt, dann wird der try-Block bis zum Ende ausgeführt, und der catch-Block wird nicht ausgeführt.

„Hä?“

„Stell dir vor, dass wir nach jedem Methodenaufruf prüfen, ob die Methode normal zurückgekehrt ist oder ob sie aufgrund einer Ausnahme abrupt beendet wurde. Wenn eine Ausnahme auftritt, dann führen wir den catch-Block (falls vorhanden) aus, um die Ausnahme abzufangen. Wenn es keinen catch-Block gibt, dann beenden wir die aktuelle Methode, und die Methode, die uns aufgerufen hat, führt die gleiche Prüfung durch.“

„Ich glaube, jetzt habe ich es verstanden.“

„Hervorragend.“

„Was bedeutet dieses ‚Exception‘ in der catch-Anweisung?“

„Alle Ausnahmen sind Klassen, die von der Klasse Exception erben. Wir können eine bestimmte Ausnahme abfangen, indem wir die Ausnahmeklasse im catch-Block angeben, oder wir können alle Ausnahmen abfangen, indem wir ihre gemeinsame Elternklasse angeben: Exception. Dann können wir alle notwendigen Fehlerinformationen aus der Variable e abrufen (diese speichert eine Referenz auf das Ausnahmeobjekt).“

„Cool! Wenn in meiner Methode verschiedene Ausnahmen auftreten, kann ich sie dann unterschiedlich verarbeiten?“

„Das kannst du nicht nur, dass solltest du sogar. Und das geht folgendermaßen:“

Beispiel:
public class ExceptionExample2
{
    public static void main(String[] args)
    {
        System.out.println("Program starts");

        try
        {
            System.out.println("Before calling method1");
            method1();
            System.out.println("After calling method1. This will never be shown");
        }
        catch (NullPointerException e)
        {
           System.out.println("Null reference. Exception has been caught");
        }
        catch (ArithmeticException e)
        {
            System.out.println("Division by zero. Exception has been caught");
        }
        catch (Exception e)
        {
            System.out.println("Any other errors. Exception has been caught");
        }

        System.out.println("Program is still running");
    }

    public static void method1()
    {
        int a = 100;
        int b = 0;
        System.out.println(a / b);
    }
}

„Der try-Block kann mit mehreren catch-Blöcken kombiniert werden, von denen jeder die angegebenen Arten von Ausnahmen abfängt.“

„Ich glaube, das verstehe ich. Selbst kann ich das zwar noch nicht schreiben, aber wenn ich es im Code entdecke, bekomme ich keinen Schreck.“