1. Einführung
In der Java-Standardbibliothek gibt es bereits viele Exceptions: NullPointerException, IllegalArgumentException, IOException und andere. Dennoch reichen die Standard-Exceptions manchmal nicht aus, um einen in Ihrem Programm aufgetretenen Fehler klar und verständlich zu beschreiben.
Beispiel aus der Praxis:
Sie entwickeln eine Banking‑Anwendung. Ein Benutzer versucht, mehr Geld abzuheben, als sich auf dem Konto befindet. Man könnte eine IllegalArgumentException werfen. Verständlicher ist es jedoch, eine eigene Exception zu verwenden, z. B. InsufficientFundsException. Dann ist im Code sofort ersichtlich, was passiert ist.
Eigene Exceptions sind eine sinnvolle Form der Anpassung. Sie erlauben eine feine Steuerung im Umgang mit Problemen, und anhand ihres Namens (sofern sinnvoll gewählt!) ist sofort klar, was passiert ist. Außerdem besitzen sie eine gewisse Selbstdokumentation: Methoden mit throws MyException in der Signatur machen unmittelbar deutlich, welche Fehler auftreten können. Und zusätzlich kann man jederzeit weitere Felder hinzufügen (z. B. Kontostand, Transaktionsbetrag usw.).
2. Wie erstellt man eine eigene Exception?
Ganz einfach: Erstellen Sie eine neue Klasse, die von einer der Standard-Exception-Klassen erbt.
- Für geprüfte (checked) Exceptions – von Exception erben.
- Für ungeprüfte (unchecked) – von RuntimeException erben.
Beispiel: geprüfte Exception
public class InvalidCredentialsException extends Exception {
public InvalidCredentialsException(String message) {
super(message); // Wir übergeben die Meldung an die Basisklasse
}
}
Nun können Sie diese Exception in Ihrem Code werfen:
if (!login.equals("admin") || !password.equals("1234")) {
throw new InvalidCredentialsException("Ungültiger Benutzername oder Passwort");
}
Beispiel: ungeprüfte Exception
public class NegativeBalanceException extends RuntimeException {
public NegativeBalanceException(String message) {
super(message);
}
}
Wann checked und wann unchecked verwenden?
- Checked – wenn der Fehler erwartet wird und behandelt werden kann (z. B. Validierungsfehler, fehlende Datei, ungültige Benutzerdaten).
- Unchecked – wenn der Fehler auf einen Bug in der Programmlogik hindeutet (z. B. Division durch Null, Verletzung eines Invarianten).
3. Konstruktoren: Wie macht man eine Exception aussagekräftig
Üblicherweise implementiert man in der eigenen Exception-Klasse mindestens einen Konstruktor mit dem Parameter String message. Häufig fügt man jedoch weitere hinzu:
public class ScoreLimitExceededException extends Exception {
public ScoreLimitExceededException() {
super();
}
public ScoreLimitExceededException(String message) {
super(message);
}
public ScoreLimitExceededException(String message, Throwable cause) {
super(message, cause);
}
public ScoreLimitExceededException(Throwable cause) {
super(cause);
}
}
Erläuterung:
- message – die textuelle Beschreibung des Fehlers.
- cause – die Ursache (eine andere Exception), falls Sie einen Fehler „verpacken“ möchten.
Tipp: Wenn Sie nicht sicher sind, welche Konstruktoren Sie brauchen – fügen Sie zumindest denjenigen hinzu, der eine Zeichenkette annimmt.
4. Eigene Exceptions im Code verwenden
Betrachten wir ein Beispiel: Wir haben einen Benutzer, der Benutzer hat Punkte, und es dürfen nicht mehr als 100 hinzugefügt werden.
public class User {
private String name;
private int score;
public User(String name) {
this.name = name;
this.score = 0;
}
public void addScore(int points) throws ScoreLimitExceededException {
if (score + points > 100) {
throw new ScoreLimitExceededException("Punktelimit überschritten! Versuch, hinzuzufügen: " + points);
}
this.score += points;
}
}
Exception-Klasse:
public class ScoreLimitExceededException extends Exception {
public ScoreLimitExceededException(String message) {
super(message);
}
}
Behandlung:
try {
user.addScore(60);
user.addScore(50); // Hier wird eine Exception geworfen!
} catch (ScoreLimitExceededException e) {
System.out.println("Fehler: " + e.getMessage());
}
Ergebnis:
Fehler: Punktelimit überschritten! Versuch, hinzuzufügen: 50
Vielleicht fragen Sie sich: Was ist, wenn man statt einer Exception einfach die Bedingung if verwendet und z. B. false oder einen anderen speziellen Rückgabewert nutzt, um anzuzeigen, dass die Operation fehlgeschlagen ist? Zum Beispiel so:
public boolean addScore(int points) {
if (score + points > 100) {
return false; // Oder eine RuntimeException werfen, wenn Sie sie nicht behandeln möchten
}
this.score += points;
return true;
}
Auch wenn dieser Ansatz einfacher erscheinen mag, hat er Nachteile, wenn es um schwerwiegende Fehler oder Verstöße gegen die Logik der Anwendung geht.
Erstens verpflichtet die Rückgabe von false oder eines anderen Werts zur Fehlerkennzeichnung den aufrufenden Code, den Rückgabewert ausnahmslos zu prüfen. Vergisst ein Entwickler das, kann der Fehler unbemerkt bleiben, was zu unvorhersehbarem Verhalten führt. Exceptions hingegen erzwingen die Behandlung (bei geprüften Exceptions) oder signalisieren das Problem deutlich, wenn sie nicht abgefangen werden.
Zweitens transportieren Exceptions die Semantik des Fehlers klarer. Die Rückgabe von false kann alles Mögliche bedeuten: „fehlgeschlagen“, „nicht anwendbar“, „nicht verfügbar“. Die Exception ScoreLimitExceededException sagt eindeutig: „Punktelimit überschritten“. Das verbessert Lesbarkeit und Wartbarkeit des Codes.
Drittens erlauben Exceptions eine zentralisierte Fehlerbehandlung. Anstatt if-Prüfungen über den gesamten Code zu verteilen, wo addScore aufgerufen wird, können Sie die Exception an einer Stelle abfangen und eine passende Entscheidung treffen: dem Benutzer eine Meldung anzeigen, ins Log schreiben oder eine Transaktion zurückrollen.
Schließlich handelt es sich bei Problemen wie einer Limitüberschreitung tatsächlich um eine außergewöhnliche Situation (daher der Name). Der normale Programmfluss geht davon aus, dass Punkte erfolgreich hinzugefügt werden. Ist das nicht der Fall, liegt ein Verstoß gegen die Geschäftslogik oder gegen Invarianten des Objekts vor – ein idealer Fall für den Einsatz von Exceptions.
5. Zusätzliche Felder zu Exceptions hinzufügen
Mitunter ist es hilfreich, der eigenen Exception zusätzliche Daten mitzugeben, die bei der Fehlerbehandlung helfen.
Beispiel:
public class ScoreLimitExceededException extends Exception {
private int currentScore;
private int attemptedAdd;
public ScoreLimitExceededException(String message, int currentScore, int attemptedAdd) {
super(message);
this.currentScore = currentScore;
this.attemptedAdd = attemptedAdd;
}
public int getCurrentScore() {
return currentScore;
}
public int getAttemptedAdd() {
return attemptedAdd;
}
}
Verwendung:
if (score + points > 100) {
throw new ScoreLimitExceededException(
"Punktelimit überschritten!",
this.score,
points
);
}
6. Nützliche Details
Wie benennt man eigene Exceptions?
In Java ist es üblich, benutzerdefinierte Exceptions mit dem Suffix Exception zu benennen: InvalidUserInputException, InsufficientFundsException, ScoreLimitExceededException.
Nennen Sie Ihre Exceptions nicht einfach Error oder Warning – das kann andere Entwickler verwirren (und sogar Sie selbst nach ein paar Wochen).
Wo und wann sollte man eigene Exceptions werfen?
- Bei der Validierung von Benutzerdaten (z. B. leerer Name, negatives Alter).
- Bei Verstößen gegen Geschäftsregeln (z. B. Limitüberschreitung, Versuch, mehr Geld abzuheben, als auf dem Konto ist).
- Bei Fehlern beim Arbeiten mit externen Diensten (z. B. Dienst nicht verfügbar, Zeitüberschreitung).
7. Typische Fehler beim Erstellen eigener Exceptions
Fehler Nr. 1: Von der falschen Klasse erben.
Erben Sie von Exception (oder RuntimeException) und nicht von Throwable oder Error.
Fehler Nr. 2: Kein Konstruktor mit Meldung vorhanden.
Ohne einen Konstruktor, der eine Zeichenkette (String message) annimmt, bleiben Ihre Exceptions stumm und sind schwer zu debuggen.
Fehler Nr. 3: Standard-Exceptions für Geschäftslogik verwenden.
Werfen Sie keine NullPointerException oder IllegalArgumentException dort, wo eine eigene „sprechende“ Exception erforderlich ist.
Fehler Nr. 4: Übermäßiger Gebrauch eigener Exceptions.
Erstellen Sie nicht für jede Kleinigkeit eine eigene Exception-Klasse. Wenn der Fehler nicht spezifisch für Ihre Domäne ist, verwenden Sie Standard-Exceptions.
Fehler Nr. 5: Fehlende Serialisierung (selten, kommt aber vor).
Wenn Ihre Exception über das Netzwerk übertragen oder gespeichert wird, sollten Sie implements Serializable verwenden. Für einfache Anwendungen ist das jedoch nicht kritisch.
GO TO FULL VERSION