Inhalt dieser Seite
In dem sehr praxisorientierten Abschnitt 8.1 haben wir Java-Exceptions am Beispiel der selbst erstellten Klasse MyArrayList kennengelernt. Nun wollen wir das Thema etwas allgemeiner betrachten.
Bei den Ausnahmen, die zu einem Programmabbruch führen können, gibt es zwei Varianten.
Kontrollierte Ausnahmen: Das sind Exceptions, die vom Programm behandelt werden müssen.
Nicht kontrollierte Ausnahmen: Das sind Exceptions, die vom Programm zwar behandelt werden können, aber nicht zwingend behandelt werden müssen.
Schwerpunkt dieses Abschnitts 8.2 werden die nicht kontrollierten Ausnahmen sein, wie wir sie bereits bei der Erstellung der Klasse MyArrayList kennengelernt haben. Zunächst aber wollen wir recht kurz über die kontrollierten Ausnahmen sprechen. Kontrollierte Ausnahmen werden uns begegnen, sobald wir anfangen, mit Dateien zu arbeiten.
Was ist eine Exception? ↑
In Java wird in einer Ausnahmesituation ein Exception-Objekt erzeugt und "geworfen" (throw). Wenn das Programm diese Exception nicht behandelt, beendet die JVM (Java Virtual Machine) das Programm und gibt eine Fehlermeldung aus.
In dem Buch von D. Abts, Grundkurs Java von 2020, ist auf Seite 127 sehr übersichtlich dargestellt, wie das Grundprinzip dieses Ausnahme-Mechanismus aussieht. Ich übernehme diese Übersicht einmal komplett als Zitat:
- Das Laufzeitsystem erkennt eine Ausnahmesituation bei der Ausführung einer Methode, erzeugt ein Objekt einer bestimmten Klasse (Ausnahmetyp) und löst damit eine Ausnahme aus.
- Die Ausnahme kann entweder in derselben Methode abgefangen und gleich behandelt werden oder sie kann an die aufrufende Methode weitergereicht werden.
- Die Methode, an die die Ausnahme weitergereicht wurde, hat nun ihrerseits die Möglichkeit, diese entweder abzufangen und zu behandeln oder ebenfalls weiterzureichen.
- Wird die Ausnahme nur immer weitergereicht und in keiner Methode behandelt, bricht das Programm mit einer Fehlermeldung ab.
Throw und catch
Autor: Ulrich Helmich 03/2026, Lizenz: Public domain
Dieses Bild zeigt das Zusammenwirken von throw und catch an einem bekannten Beispiel.
8.2.1 Kontrollierte Ausnahmen ↑
Kontrollierte Ausnahmen (checked exceptions) sind solche, die vom Compiler ausdrücklich überwacht werden.
Das heißt: Wenn eine Methode eine solche kontrollierte Ausnahme auslösen kann, zwingt der Compiler den Entwickler, sich darum zu kümmern. Die Methode muss die Ausnahme entweder
- mit einem try-catch-Block behandeln oder
- mit dem Schlüsselwort throws in der Methodensignatur deklarieren und an die aufrufende Methode weitergeben, die sich dann um die Fehlerbehandlung kümmert.
Wird eine solche kontrollierte Ausnahme nicht behandelt, kann der Code nicht kompiliert werden.
Das ist ein wichtiger Unterschied zu den nicht kontrollierten Ausnahmen. Wenn wir in den Quelltext beispielsweise den folgenden Code schreiben
for (int i = 0; i < array.length + 3; i++) System.out.println(array[i]);
dann wird der Compiler weder eine Fehlermeldung ausgeben noch eine Behandlung dieser Ausnahme verlangen. Erst zur Laufzeit tritt dann eine ArrayIndexOutOfBoundsException auf, und das Programm bricht ab, wenn die Ausnahme nicht abgefangen wurde. Diese Ausnahme muss also nicht zwingend behandelt werden.
Typische kontrollierte Ausnahmen
IOException
Allgemeiner Ein-/Ausgabefehler, zum Beispiel wenn aus einer Datei gelesen werden soll, die aus irgendeinem Grund nicht lesbar ist, oder wenn auf der Festplatte ein Schreibfehler vorliegt, die Netzwerkverbindung während des Lesens oder Schreibens abbricht und so weiter.
FileNotFoundException
Die FileNotFoundException ist eine Unterklasse von IOException. Eine solche Ausnahme tritt beispielsweise auf, wenn eine Datei, auf die zugegriffen werden soll, gar nicht existiert oder wenn keine Zugriffsrechte für die Datei vorhanden sind.
SQLException
Diese Ausnahme bezieht sich auf Fehler bei Zugriffen auf eine Datenbank. Ursache hierfür können falsche Zugangsdaten, fehlerhafte SQL-Anweisungen oder ein nicht erreichbarer Datenbank-Server sein.
Weitere kontrollierte Ausnahmen
Es gibt noch eine ganze Reihe weiterer kontrollierter Ausnahmen wie ClassNotFoundException, InterruptedException, ParseException oder MalformedURLException.
Gemeinsames Merkmal all dieser Exceptions ist, dass sie meistens bei Dateioperationen, Netzwerkzugriffen, Datenbankzugriffen oder bei der Thread-Programmierung auftreten.
Eine Methode muss solche kontrollierten Ausnahmen entweder selbst behandeln oder mit throws an die aufrufende Methode weitergeben, die sich dann um die Fehlerbehandlung kümmern muss.
8.2.2 Nicht kontrollierte Ausnahmen ↑
Wie bereits zu Beginn dieser Seite gesagt, haben wir mit den nicht kontrollierten Ausnahmen bereits jede Menge Erfahrungen gemacht, als wir die Klasse MyArrayList entwickelten.
1. IllegalArgumentException ↑
Codebeispiel 1
public MyArrayList(int startKapazitaet)
{
if (startKapazitaet <= 0)
throw new IllegalArgumentException
("Ungueltige Startkapazitaet: " + startKapazitaet);
elementData = (T[]) new Object[startKapazitaet];
size = 0;
}
Dieses Beispiel stammt aus der Klasse MyArrayList, die wir hier entwickelt haben. Die IllegalArgumentException dient dazu, unzulässige Argumente oder Parameterwerte kenntlich zu machen.
Codebeispiel 2
public class Kreis
{
private double radius;
public Kreis(double radius)
{
if (radius <= 0)
{
throw new IllegalArgumentException
("Der Radius muss positiv sein.");
}
this.radius = radius;
}
public double getFlaeche()
{
return Math.PI * radius * radius;
}
}
Wenn hier also für den Radius des Kreises ein negativer Wert übergeben wird, dann wird eine IllegalArgumentException geworfen. Die JVM würde einen negativen Wert für radius durchaus akzeptieren und das Programm nicht automatisch beenden.
Daher unterscheidet sich die IllegalArgumentException von Exceptions wie ArithmeticException, ArrayIndexOutOfBoundsException oder NullPointerException, die in typischen Fehlersituationen oft automatisch zur Laufzeit entstehen.
Auf der Lexikonseite IllegalArgumentException sowie im Skript finden Sie zwei weitere Codebeispiele aus dem Waage-Projekt (Folge 2).
Mögliche Klausuraufgabe
Eine mögliche Aufgabe in der Klausur könnte darin bestehen, dass Sie den Quelltext der Klasse Waage erhalten und die Methoden dann um Exceptions und try-catch-Blöcke erweitern müssen.
2. IndexOutOfBoundsException ↑
Diese Exception wird verwendet, wenn man mit Listen oder ähnlichen Datenstrukturen arbeitet und ein Index außerhalb des zulässigen Bereichs liegt. Bei Listen wie ArrayList tritt in solchen Fällen meist eine IndexOutOfBoundsException auf, bei richtigen Arrays dagegen eine ArrayIndexOutOfBoundsException. Diese Ausnahme ist eine Unterklasse der IndexOutOfBoundsException.
Codebeispiel 1
private void checkElementIndex(int index)
{
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException
("index ausserhalb: " + index);
}
Dies ist eine private Hilfsmethode aus der Klasse MyArrayList. Der Parameter index darf einerseits nicht negativ sein, denn Array-Elemente mit negativen Indizes gibt es nicht. Andererseits darf index auch nicht den Wert size oder einen noch größeren Wert haben. Beide Bedingungen werden in der if-Anweisung geprüft. Ist eine von ihnen erfüllt, wird eine IndexOutOfBoundsException geworfen.
Codebeispiel 2
public class DemoArray
{
public static void main(String[] args)
{
int[] zahlen = new int[3];
zahlen[0] = 10;
zahlen[1] = 20;
zahlen[2] = 30;
System.out.println(zahlen[3]);
}
}
Ein Array-Element mit dem Index 3 existiert nicht; dieser Index liegt außerhalb des gültigen Bereichs 0 bis 2. Die JVM erzeugt hier dann eine ArrayIndexOutOfBoundsException.
Aufgabe für zwischendurch
Was ist an dem folgenden Code falsch - und warum?
public class Kreis
{
private double radius;
public Kreis(double radius)
{
if (radius <= 0)
throw new IndexOutOfBoundsException
("ungueltiger Wert: " + radius);
this.radius = radius;
}
}
Das wäre übrigens auch eine schöne kleine Klausuraufgabe.
Wir wollen die in der Aufgabe gestellte Frage gleich beantworten:
Dieses Beispiel ist technisch zwar möglich, aber inhaltlich nicht sinnvoll. Ein negativer oder zu kleiner Radius hat nichts mit einem Index zu tun.
Was lernen wir aus diesem Negativ-Beispiel? Man sollte im Code nicht mit Exceptions um sich herum werfen, sondern immer genau überlegen, welche Exception zu dem erwarteten Fehler passt. Hier wäre also eher eine IllegalArgumentException angebracht.
Auf der Lexikonseite IndexOutOfBoundsException sowie im Skript finden Sie zwei weitere Codebeispiele.
Was ist der Unterschied zwischen der IndexOutOfBoundsException und der ArrayIndexOutOfBoundsException?
Eine ArrayIndexOutOfBoundsException tritt typischerweise dann auf, wenn bei einem tatsächlichen Array-Zugriff ein ungültiger Index verwendet wird, also etwa bei zahlen[5] in einem zu kleinen Array. Sie wird dann automatisch von der JVM ausgelöst.
Wenn man einen Index jedoch vorher selbst prüft und bei einem unzulässigen Wert die Exception mit throw auslöst, verwendet man meist allgemeiner eine IndexOutOfBoundsException. Das gilt auch dann, wenn sich die Prüfung auf ein Array bezieht.
Die IndexOutOfBoundsException ist in solchen Fällen die bessere Wahl.
3. ArithmeticException ↑
Eine ArithmeticException tritt bei ganzzahliger Division durch 0 auf; andere Anwendungsfälle für diese Exception sind sehr selten. Betrachten wir einmal die folgende for-Schleife:
for (int i = 10; i > -10; i--) x = y / i;
Sobald die Laufvariable i bei ihrem Abstieg den Wert 0 erreicht hat, versucht das Programm, durch 0 zu dividieren. Dabei löst die JVM eine ArithmeticException aus.
Die folgende Variante löst ebenfalls eine ArithmeticException aus, diesmal jedoch ausdrücklich mit throw:
for (int i = 10; i > -10; i--)
{
if (i == 0)
throw new ArithmeticException("Division durch 0!");
x = y / i;
}
Hier wird die Exception also nicht erst durch die Division verursacht, sondern bereits vorher absichtlich ausgelöst.
Das gilt übrigens nur für die ganzzahlige Division, also etwa bei int oder long. Bei double oder float führt eine Division durch 0 nicht zu einer ArithmeticException, sondern zu speziellen Werten wie Infinity oder NaN.
Vergleichen wir einmal an einem einfachen Beispiel, welche Vorteile uns das Exception-Handling bringt.
Bisher
Bisher, also bevor wir uns mit Exceptions beschäftigt hatten, mussten wir eine Methode wie quotient() so implementieren:
public double quotient(double zaehler, double nenner)
{
if (nenner == 0)
{
System.out.println("Division durch 0!");
return 0;
}
else
return zaehler / nenner;
}
Der Zähler wird hier als Parameter übergeben und kann durchaus auch einmal den Wert 0 haben; die Methode quotient() muss mit dieser Möglichkeit rechnen und darauf vorbereitet sein. In diesem Fall wird dann eine Fehlermeldung in der Konsole ausgegeben und die Methode mit return 0 verlassen. Irgendein double-Wert muss auf jeden Fall mit return zurückgegeben werden, da die Signatur der Methode dies ausdrücklich verlangt.
Jetzt
public double quotient(double zaehler, double nenner)
{
if (nenner == 0)
throw new ArithmeticException("Division durch 0!");
return zaehler / nenner;
}
Wenn der Nenner den Wert 0 haben sollte, wird eine ArithmeticException geworfen, um die sich dann die aufrufende Methode kümmern muss - entweder durch Weiterreichen oder durch eine eigene Behandlung mit try-catch.
Übrigens ist der Ausdruck nenner == 0 nicht optimal für double-Werte, aber das ist ein anderes Thema und soll hier nicht vertieft werden.
Diese Seite in der Abteilung "Begriffe und Konzepte der OOP" enthält im Wesentlichen den gleichen Text wie in diesem Abschnitt, aber ergänzt um einen Abschnitt über seltene Fälle von ArithmeticException.
4. NullPointerException ↑
Wenn man größere Programme mit vielen Klassen und Objekten schreibt, kommt eine NullPointerException recht häufig vor. Eine solche Exception entsteht immer dann, wenn man über eine Referenz auf ein Objekt zugreifen möchte, diese Referenz aber den Wert null hat. In diesem Fall existiert also kein Objekt, auf das tatsächlich zugegriffen werden könnte.
Die Ursache ist oft, dass ein Objekt nicht erzeugt wurde, eine Referenz-Variable nie einen sinnvollen Wert erhalten hat oder dass ein Array zwar angelegt wurde, seine einzelnen Elemente aber noch nicht mit Objekten gefüllt wurden.
Codebeispiel 1
public class DemoNull
{
public static void main(String[] args)
{
String text = null;
System.out.println(text.length());
}
}
Wie jede Variable vom Typ String ist auch text eine Referenz-Variable. Sie kann also auf ein konkretes Objekt im Speicher verweisen. In diesem Beispiel wurde text jedoch ausdrücklich auf null gesetzt. Die Variable verweist daher auf kein einziges Objekt.
Der Aufruf von text.length() führt deshalb zu einem Laufzeitfehler: Es wird eine NullPointerException geworfen.
Bei diesem Beispiel sieht man die Fehlerursache sofort. Als Nächstes schauen wir uns ein Beispiel an, bei dem die Fehlerursache nicht ganz so augenfällig ist.
Codebeispiel 2
public class NullDemo
{
public static void main(String[] args)
{
Person[] personen = new Person[3];
personen[0] = new Person("Meier");
personen[1] = new Person("Schulze");
for (int i = 0; i < personen.length; i++)
{
System.out.println(personen[i].getName());
}
}
}
Hier wurde ein Array für drei Objekte der Klasse Person angelegt. Dabei ist aber zunächst nur das Array selbst erzeugt worden. Die einzelnen Array-Elemente enthalten anfangs noch den Wert null.
Anschließend werden nur die Elemente personen[0] und personen[1] mit echten Person-Objekten belegt. Das dritte Element personen[2] bleibt dagegen null.
Im letzten Durchgang der for-Schleife wird daher versucht, getName() für personen[2] aufzurufen. Da dort aber gar kein Objekt vorhanden ist, entsteht eine NullPointerException.
Diese Seite im Bereich "Begriffe und Aspekte der OOP" enthält den gleichen Text wir hier, aber zwei zusätzliche Code-Beispiele und eine KI-generierte Zusammenfassung.
5. IllegalStateException ↑
Auch diese Exception kann von den Entwicklern einer Anwendung bewusst eingesetzt werden, wenn ein Objektzustand den Methodenaufruf eigentlich nicht zulassen sollte. Ein typisches Beispiel ist eine Klasse Motor mit den Methoden starten() und stoppen(). Wenn der Motor schon läuft, sollte die Methode starten() nicht mehr aufgerufen werden können. Wird sie dennoch aufgerufen, sollte eine entsprechende Fehlermeldung erscheinen.
Codebeispiel
public class Motor
{
private boolean gestartet;
public void starten()
{
if (gestartet)
{
throw new IllegalStateException("Der Motor läuft bereits.");
}
gestartet = true;
}
public void stoppen()
{
if (!gestartet)
{
throw new IllegalStateException("Der Motor läuft noch nicht.");
}
gestartet = false;
}
}
6. InputMismatchException ↑
Eine InputMismatchException tritt auf, wenn ein Programm mit einem Scanner einen Wert eines bestimmten Datentyps einlesen möchte, die Benutzereingabe aber nicht zu diesem Datentyp passt.
Das ist zum Beispiel der Fall, wenn mit Scanner.nextInt() eine ganze Zahl eingelesen werden soll, der Benutzer aber einen Text oder eine Kommazahl eingibt.
Die Ausnahme zeigt also an, dass die Eingabe nicht dem erwarteten Format entspricht.
Weiter werden wir an dieser Stelle nicht auf die InputMismatchException eingehen. Wenn wir uns in einer späteren Folge mit der Klasse Scanner und ihren Verwandten beschäftigt haben, werden wir auch noch einmal auf diese Exception zurückkommen.
Merke:
Laufzeitfehler
- werden vom Compiler nicht erkannt und nicht verhindert,
- treten erst während der Ausführung des Programms (zur Laufzeit) auf
- führen zum Programmabbruch, wenn sie nicht korrekt abgefangen werden.
Die wichtigsten Laufzeitfehler sind:
- ArithmeticException: Es wird versucht, eine ganze Zahl durch 0 zu teilen
- ArrayIndexOutOfBoundsException: Es wird ein Array-Element mit einem ungültigen Index aufgerufen.
- NullPointerException: Es wird auf eine Referenz mit dem Wert null zugegriffen.
- IllegalArgumentException: Es wird ein fehlerhafter Parameter übergeben.
- IllegalStateException: Es wird auf eine ungeeignete Methode zugegriffen.