Während der Ausführung eines Programms können unterschiedliche Ausnahmesituationen auftreten. Zum Beispiel könnte bei einer Berechnung durch Null dividiert werden, eine zu bearbeitende Datei kann nicht gefunden werden oder eine Methode erhält einen ungültigen Parameter. Ganz allgemein kann man solche Situationen als Zustände bezeichnen, die das Programm irgendwie daran hindern, im normalen Ablauf fortzufahren. Solche Situationen sind natürlich unerwünscht und sollen durch Konzepte zur Ausnahmebehandlung vermieden werden.
In vielen Programmiersprachen, die kein Konzept zur Ausnahmebehandlung bereithalten, werden solche Ausnahmesituationen meist mit bestimmten Rückgabewerten gekennzeichnet. Die Rückgabewerte entsprechen dann einer vorab festgelegten Definition von möglichen Fehlersignalen (z.B. "-1" für "Division durch Null"). Diese Herangehensweise hat jedoch einen großen Nachteil: Weil Funktionen nur einen Rückgabewert haben, müssen technische Fehlersignale und fachliche Rückgabewerte gleichermaßen über diesen Rückgabewert an das aufrufende Programm zurückgegeben werden. Diese Vermischung von technischen Signalen und fachlichen Werten führt zu schlechter Softwarequalität und sollte daher vermieden werden.
Um robustere Programme schreiben zu können, sollte es also einen eigenen Kanal für die Signalisierung von Fehlern geben, der zur Entkoppelung von fachlichen Rückgabewerten und Fehlersignalen führt. Eine eigene Datenstruktur für solche Signale böte zudem den Vorteil, differenziertere Fehlermeldungen definieren zu können. Zuletzt wäre es wünschenswert, alle aufrufenden Programme dazu zu zwingen, mögliche Ausnahmen einer aufgerufenen Methode abfangen zu müssen. Denn dadurch könnte sichergestellt werden, dass Lösungsstrategien zu häufigen Fehlerquellen existieren.
Die Programmiersprache Java bietet mit der Klasse Exception ein objektorientiertes Konzept zur Definition und Verwendung von Ausnahmen. Neben einer umfangreichen Sammlung vordefinierter Standard-Exceptions gibt es auch spezielle Sprachelemente zum Erzeugen ("Werfen") und Abfangen von Exceptions.
Ein Auszug vordefinierter Standard-Exceptions:
| Name | Beschreibung | Beispiel |
ArithmeticException |
Division durch Null | int n = 0; |
ArrayIndexOutOfBoundsException |
Zugriff auf ein Listenelement außerhalb des definierten Bereichs |
int[] zahlen = new int[8]; |
NullPointerException |
Zugriff auf ein nicht instanziiertes Objekt | kunde k = null; |
Java bietet mehrere Alternativen, wie mit einer Exception umgegangen werden soll:
Die folgende Abbilgung zeigt die erste Alternative anhand eines Codebeispiels. Sie soll verdeutlichen, wie die Ausnahme
erkannt und behandelt werden kann, ohne dass das aufrufende Programm darüber informiert wird. Der fehleranfällige
Programmabschnitt wird von einem try-Block umschlossen. Mithilfe des catch-Blocks kann angegeben werden,
welche im try-Block auftretenden Ausnahmen abgefangen werden sollen und wie sie behandelt werden sollen. Im Beispiel wird
das Ergebnis auf 0 gesetzt, wenn kein Artikel im Warenkorb liegt.

Das obige Codebeispiel profitiert aber noch nicht von allen Vorteilen der expliziten Ausnahmebehandlung. Hilfreicher als das
bloße Zurückgeben des Wertes 0 ist die Weiterleitung der Exception an das aufrufende Programm. Das macht inbesondere dann
Sinn, wenn das aufrufende Programm die Verantwortung zur Behandlung der Exception erhalten soll. Damit Methoden Exceptions
weiterleiten können, muss ihre Signatur um das Schlüsselwort throws erweitert werden. Es folgt die Auflistung
aller Exceptions, die in der Methode auftreten können. So können aufrufende Programme anhand der Signatur erkennen,
welche Ausnahmen von dieser Methode zu erwarten sind. Die Ausnahmebehandlung per try/catch-Block muss in das aufrufende
Programm verlagert werden.

Als aufrufendes Programm ist es nun die Aufgabe des OnlineShops, die Ausnahmen zu fangen und zu behandeln. Um einen möglichen Programmabbruch wie oben abgebildet zu verhindern, wird der Aufruf der Methode preisProArtikel() jetzt in einen try/catch-Block eingebettet. Im Ausnahmefall wird über die Methode getMessage() des Exception-Objekts die Fehlermeldung ermittelt und ausgegeben. Im Anschluss kann das Programm mit den folgenden Anweisungen fortfahren.
| Merke |
| Es ist an dieser Stelle zu beachten, dass nicht alle in Java integrierten Standard-Exceptions behandelt werden müssen. Beispielsweise führt das Weglassen des try/catch-Block im obigen Beispiel nicht zu einem Compilefehler, obwohl die Signatur der Methode preisProArtikel() darauf hinweist, dass sie eine Exception wirft. Solche Exceptions werden in der Literatur "unchecked" Exceptions genannt. Man kann sie daran erkennen, dass sie alle Unterklassen von RuntimeException sind. Alle anderen Exceptions, also auch die selbst definierten sind "checked" Exceptions und müssen abgefangen werden. |
Das oben aufgeführte Codebeispiel hat einen Nachteil: Die Fehlermeldung ist für den Benutzer des OnlineShops
unverständlich, da sie nicht die Ursache (Warenkorb ist leer), sondern nur die Wirkung (Division durch Null) nennt.
Viel besser wäre also eine kontextbezogene Fehlermeldung, die auf das tatsächliche Problem hinweist. Daher wird
im Folgenden die dritte Alternative vorgestellt: Das Weiterleiten der Exception mit dem Schlüsselwort throw
und einer spezifischen Fehlermeldung. Hierzu wird in der Methode preisProArtikel() die Exception wieder per try/catch-Block
gefangen, um dann wieder eine neue ArithmeticException zu werfen, die die spezifische Fehlermeldung enthält. Die
angepasste Fehlermeldung wird dem Konstruktor der Exception als Parameter übergeben.

Bei der dritten Variante ändert sich beim aufrufenden Programm nichts, da es die deklarierte Exception weiterhin abfangen muss. Lediglich die Fehlermeldung der zu behandelnden Exception hat sich verändert.
Ergänzend sei noch darauf hingewiesen, dass zu einem try-Block auch mehrere catch-Blöcke definiert werden
können. Das macht zum Beispiel dann Sinn wenn
Angenommen, die Methode preisProArtikel() wäre deutlich komplexer und könnte neben der ArithmeticException auch
eine ArrayIndexOutOfBoundsException auslösen. In diesem Fall könnte ein erweiterter try/catch-Block wie in
untenstehender Abbildung aussehen. Wenn eine Ausnahme im try-Block auftritt, wird der Reihe nach geprüft, welcher
catch-Block zu der geworfenen Ausnahme passt. Sollte keiner der speziellen catch-Blöcke passend sein, wird die
Ausnahme im allgemeinen catch-Block abgefangen, da alle Ausnahmen in Java Unterklassen von Exception sind.

Die Ausnahmebehandlung mit try/catch-Blöcken bietet viele Vorteile. Allerdings gibt es auch eine bestimmte Situation,
in der die bislang bekannten Sprachkonstrukte das Programm verkomplizieren: Wenn nach der Ausführung des try-Blocks
in jedem Fall eine bestimmte Programmlogik aufgerufen werden soll (z.B. Aufräumarbeiten), lässt sich das mit
den bislang bekannten Mitteln nur sehr umständlich lösen. Denn zusätzlich zum normalen Ablauf ohne Exceptions
gibt es gleich drei weitere Möglichkeiten, wie der try-Block verlassen werden kann (die 3 catch-Blöcke). In jedem
dieser Fälle müsste man dafür sorgen, dass unbedingt notwendige Aufräumarbeiten durchgeführt werden.
Um solchen doppelten Code zu vermeiden, gibt es den finally-Block. Er wird nach den catch-Blöcken definiert und in
jedem Fall ausgeführt - egal ob und welche Exception aufgetreten ist.

Beispiel einer eigenen Exception:

Jede Ausnahme, egal ob eingebaute Standard-Exception oder eigens definierte Exception, muss von der Oberklasse Exception
erben. Mithilfe der Konstruktoren kann festgelegt werden, welche Nachrichten von der Exception erzeugt werden. Wenn zur
Erzeugung der Standard-Konstruktor verwendet wird, sollte eine Standard-Fehlermeldung festgelegt werden. Zu diesem Zweck
wird der Konstruktor der Oberklasse (Exception) mit der gewünschten Zeichenkette als Parameter aufgerufen. Er
interpretiert den übergebenen Parameter als Fehlermeldung und stellt sie über die Methode getMessage() dem
aufrufenden Programm zur Verfügung. Wenn man die Ausnahme mit einer erst zur Laufzeit bekannten Fehlermeldung erzeugen
möchte, kann man einen weiteren Konstruktor mit Parameter hinzufügen, der die übergebene Fehlermeldung
unmittelbar an den Konstruktor der Oberklasse weiterreicht.

Um die eigene Exception zu fangen, wird der Aufruf von getMindestbestellwert() einfach in einen try/catch-Block
eingebettet.
