Zum Inhalt
wannacode

Werfen und eigene Exceptions

Kurz gesagt: Mit throw lehnt eine Methode ab und wirft eine Exception, die durch die Aufrufe fällt, bis ein catch sie fängt; für deinen eigenen Fall schreibst du eine eigene Exception-Klasse.

Problem

Der Geldautomat sagt „Nimm dein Geld“

Das Konto speichert seinen Kontostand. Wenn der Betrag zum Abheben negativ ist oder das Geld nicht reicht, tut die Methode einfach nichts:

public void withdraw(int amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}

Der Geldautomat benutzt sie so:

Account account = new Account(100);
account.withdraw(500);
System.out.println("Nimm dein Geld.");
System.out.println("Kontostand: " + account.getBalance() + " €");
Ausgabe
Nimm dein Geld.
Kontostand: 100 €

Der Kunde will 500 € und hat 100 €. Die Methode lehnt still ab, der Geldautomat merkt nichts und sagt „Nimm dein Geld.“ Der Fehler geht verloren. Die Methode könnte true oder false zurückgeben, aber der Aufrufer vergisst leicht, das zu prüfen, und false sagt nicht, warum.

Die Idee

throw: Ich lehne ab und sage, warum

Statt zu schweigen, wirft die Methode eine Exception:

public void withdraw(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Der Betrag muss positiv sein");
}
if (amount > balance) {
throw new IllegalArgumentException("Nicht genug Geld");
}
balance -= amount;
}

throw beendet die Methode wie return. Die Zeile balance -= amount wird nie erreicht. Aber statt eines Werts schickt die Methode eine Exception, und der Aufrufer kann sie nicht übersehen:

Account account = new Account(100);
try {
account.withdraw(500);
System.out.println("Nimm dein Geld.");
} catch (IllegalArgumentException e) {
System.out.println("Abgelehnt: " + e.getMessage());
}
System.out.println("Kontostand: " + account.getBalance() + " €");
Ausgabe
Abgelehnt: Nicht genug Geld
Kontostand: 100 €
  • new erzeugt das Exception-Objekt wie jedes andere Objekt. Die Meldung kommt in seinen Konstruktor.
  • IllegalArgumentException ist die fertige Exception für „das Argument ist falsch“. Du benutzt sie genau bei solchen Prüfungen.
Genauer, für Neugierige
  • Eine weitere häufige fertige Exception ist IllegalStateException: Das Objekt ist nicht im richtigen Zustand für diese Aktion, etwa beim Abheben von einem geschlossenen Konto.
  • throw funktioniert mit jedem Objekt einer Unterklasse von Throwable. Einen String oder eine Zahl kannst du nicht werfen.

Sieh es dir an

Die Exception fällt durch

public void withdraw(int amount) {
if (amount > balance) {
throw new IllegalArgumentException("Nicht genug Geld");
}
balance -= amount;
}
static void payRent(Account account) {
account.withdraw(800);
System.out.println("Die Miete ist bezahlt.");
}
try {
payRent(new Account(500));
} catch (IllegalArgumentException e) {
System.out.println("Abgelehnt: " + e.getMessage());
}
Schritt für Schritt, in Worten
  1. main ruft payRent für ein Konto mit 500 € auf.
  2. payRent ruft withdraw(800) auf: ein neuer Frame oben drauf.
  3. 800 > 500: Die Methode wirft eine Exception.
  4. Der Kontostand ändert sich nicht: Der Frame wird verworfen, ohne seine Zeilen zu Ende auszuführen.
  5. payRent hat kein catch: „Die Miete ist bezahlt.“ wird nicht ausgegeben, und der Frame wird verworfen.
  6. Das catch in main fängt die Exception, und das Programm macht hier weiter.

Erinnere dich an den Aufrufstapel aus der Lektion über Rekursion. main ruft payRent auf, und diese Methode ruft withdraw auf. Die Frames liegen übereinander.

Wenn withdraw wirft, sucht die Exception ein catch, indem sie den Stapel hinuntergeht. Ein Frame ohne passendes catch wird sofort verworfen, ohne seine Zeilen zu Ende auszuführen. So geht es auch payRent. Erst main hat ein catch, und das Programm macht dort weiter.

Hätte auch main kein catch, würde auch main verworfen. Dann hält das Programm an und gibt einen Stacktrace aus: genau diese Frames, von innen nach außen.

Probier es aus

Was erscheint auf dem Bildschirm?

Das Konto hat 50 €, und die Bestellung kostet 120 €.

Was gibt das Programm aus?

static void checkout(Account account) {
System.out.println("Ich bezahle die Bestellung");
account.withdraw(120);
System.out.println("Die Bestellung ist bezahlt");
}
Account account = new Account(50);
try {
checkout(account);
System.out.println("Ich sende die Bestätigung");
} catch (IllegalArgumentException e) {
System.out.println("Abgelehnt: " + e.getMessage());
}
System.out.println("Kontostand: " + account.getBalance() + " €");
Antwort

Probier es aus

Finde den Fehler

Die Miete beträgt 800 €, und auf dem Konto sind 500 €. Das Programm sagt „Die Miete ist bezahlt.“ In welcher Zeile ist der Fehler?

Wähle die Zeile mit dem Fehler.

Genauer, für Neugierige
  • Manchmal fängt eine Methode, tut etwas, etwa einen Eintrag ins Log schreiben, und wirft dieselbe Exception weiter: throw e; im catch.
  • Du kannst auch eine andere Exception werfen, die besser sagt, was passiert ist. Dann gib die alte als zweites Argument an den Konstruktor: new IllegalStateException("Die Miete ist nicht bezahlt", e). Im Stacktrace siehst du dann beide.

Die Idee

Eine eigene Exception

„Nicht genug Geld“ ist Text. Wenn der Geldautomat „Zahle noch 30 € ein“ vorschlagen will, muss er wissen, wie viel fehlt. Die Zahl aus dem Text herauszuziehen ist zerbrechlich. Schreib lieber eine eigene Exception-Klasse mit einem Feld:

class InsufficientFundsException extends RuntimeException {
private int shortfall;
public InsufficientFundsException(int shortfall) {
super("Es fehlen " + shortfall + " €");
this.shortfall = shortfall;
}
public int getShortfall() {
return shortfall;
}
}

Deine eigene Exception ist eine gewöhnliche Klasse, die von RuntimeException erbt. Der Konstruktor gibt die Meldung mit super(...) an die Basisklasse weiter. Die Methode wirft sie, wenn das Geld nicht reicht:

public void withdraw(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Der Betrag muss positiv sein");
}
if (amount > balance) {
throw new InsufficientFundsException(amount - balance);
}
balance -= amount;
}

Und der Aufrufer fängt die beiden Fälle getrennt:

Account account = new Account(100);
try {
account.withdraw(130);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
System.out.println("Zahle noch " + e.getShortfall() + " € ein oder wähle einen kleineren Betrag.");
} catch (IllegalArgumentException e) {
System.out.println("Abgelehnt: " + e.getMessage());
}
Ausgabe
Es fehlen 30 €
Zahle noch 30 € ein oder wähle einen kleineren Betrag.
Genauer, für Neugierige
  • Namen von Exceptions enden immer auf Exception.
  • Deine eigene Exception kann auch von einer spezifischeren fertigen Klasse erben, etwa IllegalArgumentException. Dann fängt ein catch für die Basisklasse auch sie. Hier ist sie getrennt, deshalb spielt die Reihenfolge der beiden catch-Blöcke keine Rolle.

Die Idee

Exception oder RuntimeException?

Wenn deine eigene Exception von Exception erbt und nicht von RuntimeException, ist sie eine Checked Exception. Die Methode muss sie mit throws deklarieren, und jeder Aufrufer muss sie fangen oder ebenfalls deklarieren:

class InsufficientFundsException extends Exception { ... }
public void withdraw(int amount) throws InsufficientFundsException {
...
}
  • RuntimeException, wenn der Fehler in den Argumenten oder in den Regeln des Programms liegt. Der Aufrufer kann ihn mit einer Prüfung verhindern. Die meisten eigenen Exceptions sind heute so.
  • Exception, wenn der Aufrufer jedes Mal darauf vorbereitet sein muss und der Compiler ihn daran erinnern soll.

Eine fertige Exception, wenn die Bedeutung passt: IllegalArgumentException für ein falsches Argument, IllegalStateException für eine Aktion im falschen Moment. Eine eigene, wenn der Aufrufer sie von den anderen unterscheiden und auf seine eigene Weise reagieren muss oder wenn die Exception Daten tragen muss, wie shortfall.

Probier es aus

Fülle die Lücke

Eine Exception für ein ungültiges Alter. Was gehört in die Lücke, damit die Meldung bei der Basisklasse ankommt?

class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String message) {
        (message);
    }
}

Übung

Exceptions in Klassen

Löse die Aufgaben in deinem Editor.

1. Musikgeschäft. Das Geschäft führt Instrumente vor, aber nicht jedes lässt sich vorführen.

  • Eine abstrakte Klasse MusicalInstrument mit Name, Marke und Preis (private Felder mit Gettern) und einem Konstruktor.
  • Ein Interface Playable mit einer Methode play().
  • Guitar und Piano erben vom Instrument und können spielen. Drum erbt nur.
  • NotPlayableException extends RuntimeException.
  • MusicShop mit einer Methode demoInstrument(MusicalInstrument instrument): Wenn das Instrument spielen kann (instanceof Playable playable), führt sie es vor, sonst wirft sie NotPlayableException.
  • Führe in main eine Gitarre Fender Stratocaster (1299 €), ein Schlagzeug Pearl Export (849 €) und ein Klavier Yamaha P-45 (499 €) aus einem Array vor. Fange die Exception für jedes Instrument einzeln, damit die Vorführung weitergeht.
Ausgabe
Fender Stratocaster, 1299.00 €
Die Gitarre spielt einen Akkord.
Pearl Export, 849.00 €
Pearl Export kann nicht vorgeführt werden.
Yamaha P-45, 499.00 €
Das Klavier spielt eine Tonleiter.

2. Ein Konto mit Regeln. Füge Account eine Methode deposit(int amount) hinzu, die bei einem Betrag von 0 oder weniger IllegalArgumentException wirft. Das Abheben wirft InsufficientFundsException aus der Lektion. Das Konto startet mit 100 €. Lies die Anzahl der Operationen und dann die Operationen selbst, jede in einer eigenen Zeile: „einzahlen 50“ oder „abheben 500“. Gib nach jeder Operation den Kontostand aus oder warum sie abgelehnt wurde.

Eingabe
4
einzahlen 50
abheben 500
abheben -5
abheben 120
Ausgabe
Kontostand: 150 €
Es fehlen 350 €. Kontostand: 150 €
Der Betrag muss positiv sein. Kontostand: 150 €
Kontostand: 30 €

3. Ungültiges Alter. Person hat einen Namen und ein Alter. setAge wirft InvalidAgeException aus der vorigen Karte, wenn das Alter unter 0 oder über 150 liegt. Auch der Konstruktor geht über setAge, damit niemand eine Person mit ungültigem Alter erzeugen kann. Erzeuge Maria (19), Peter (−3) und Ivan (200).

Ausgabe
Maria, 19 Jahre
Peter: Ungültiges Alter: -3
Ivan: Ungültiges Alter: 200

Weitere Aufgaben

  • Tischreservierung. Eine Klasse Restaurant mit einer Anzahl freier Tische und einer Methode zum Reservieren. Wenn keine Tische mehr frei sind, wirf deine eigene NoTablesException. Wenn die Zahl der Gäste 0 oder kleiner ist, wirf die fertige Exception für ein falsches Argument.
  • Ein Produkt mit Lagerbestand. Eine Klasse Product mit einem Lagerbestand und einer Methode sell(int qty). Wenn der Bestand nicht reicht, wirf OutOfStockException, die mitträgt, wie viele Stück da sind. Der Shop fängt die Exception und bietet an, die vorhandenen zu verkaufen.
  • Musikgeschäft mit Grund. Füge NotPlayableException ein Feld mit dem Instrument hinzu, das nicht spielen kann, und gib im catch seinen Preis aus.

Test

Teste dich

1. Was macht throw new IllegalArgumentException("...")?
2. main ruft a auf, a ruft b auf, b wirft eine Exception. Nur main hat ein catch. Was passiert mit den Zeilen in a nach dem Aufruf von b?
3. Wo fängst du die Exception am besten?
4. Deine eigene Exception erbt von Exception, nicht von RuntimeException. Was folgt daraus?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·