Java throw und throws
Exceptions in Java manuell mit throw auslösen und in Methodensignaturen mit throws deklarieren.
Bisher haben wir Exceptions abgefangen, die von anderem Code ausgelöst wurden. Jetzt sehen Sie, wie Sie eigene auslösen. Zwei Schlüsselwörter erledigen dabei die Hauptarbeit — und sie sind leicht zu verwechseln, weil sie sich nur um einen Buchstaben unterscheiden.
throw— eine Anweisung, die zur Laufzeit eine Exception auslöst. Ein Wort, in ausführbarem Code.throws— eine Deklaration in einer Methodensignatur, die besagt: „Diese Methode kann diese Exception-Typen werfen." Sie wird vom Compiler geprüft und nie ausgeführt.
throw passiert. throws warnt. Behalten Sie dieses Paar im Hinterkopf.
Eine Exception auslösen
throw nimmt einen Ausdruck vom Typ Throwable (oder einem Subtyp) und löst ihn aus. Die aktuelle Methode endet sofort, der Stack beginnt abgebaut zu werden, und die Exception sucht nach einem passenden catch.
if (amount < 0) {
throw new IllegalArgumentException("amount must be non-negative, got " + amount);
}Drei Details:
- Sie können nur ein
Throwablewerfen. Der Compiler erzwingt dies —throw "oops";kompiliert nicht. - Sie werfen immer eine Instanz, keine Klasse.
throw new X(...), niemalsthrow X. - Die Instanz kann eine sein, die Sie inline erstellt haben (üblich), oder ein bereits bestehendes Objekt (selten — Exceptions tragen Stack-Traces von ihrer Erstellung mit sich, daher friert die Wiederverwendung den falschen Trace ein).
Wann man werfen sollte
Werfen Sie eine Exception, wenn die aktuelle Methode ihren Vertrag nicht erfüllen kann. Einige klare Fälle:
- Ungültige Argumente —
IllegalArgumentExceptionfür „Sie haben mich falsch aufgerufen." - Falscher Zustand —
IllegalStateExceptionfür „Sie haben mich zum falschen Zeitpunkt aufgerufen" (z. B.next()auf einem leeren Iterator). - Fehlende Daten — domänenspezifische Exceptions wie
UserNotFoundException. - Fehlgeschlagene externe Operationen — IO-Fehler, Netzwerkfehler. Normalerweise kommen diese vom gerade gemachten Aufruf, daher erstellen Sie sie nicht selbst; Sie lassen sie propagieren oder umhüllen sie in einer Exception auf höherer Ebene.
Der Fall, in dem man nicht werfen sollte: als Kontrollfluss-Abkürzung für normale Ergebnisse. „Exceptions für den Kontrollfluss werfen" ist langsam und verwirrend. Wenn „nicht gefunden" ein routinemäßiges Ergebnis ist, geben Sie ein Optional<T> zurück, keine NotFoundException.
Einen Typ auswählen
Die eingebauten java.lang-Exceptions decken die meisten Fälle ohne viel Aufwand ab:
IllegalArgumentException— ungültiges ArgumentIllegalStateException— falscher ZustandNullPointerException— ein erforderliches Argument war null (verwenden SieObjects.requireNonNull)UnsupportedOperationException— Operation nicht implementiert (z. B.addeiner unveränderlichen Liste)ArithmeticException— mathematischer Fehler
Wenn der Fehler spezifisch für Ihre Domäne ist — „Benutzer nicht gefunden," „ungültiger Gutschein," „Konfiguration nicht synchron" — schreiben Sie dafür eine kleine benutzerdefinierte Klasse. In zwei Kapiteln werden wir genau das tun.
Die throws-Klausel
Wenn Ihre Methode eine checked Exception werfen könnte, die sie nicht selbst abfängt, müssen Sie sie deklarieren:
public Config loadConfig(Path p) throws IOException, ParseException {
String text = Files.readString(p);
return parser.parse(text);
}Die Klausel ist Teil des Methodenvertrags. Sie teilt jedem Aufrufer mit: „Wenn Sie mich aufrufen, müssen Sie diese Exceptions entweder abfangen oder selbst deklarieren." Der Compiler erzwingt dies — das macht sie zu checked Exceptions.
Einige Regeln:
- Sie deklarieren nur checked Exceptions.
RuntimeExceptions und ihre Unterklassen sind unchecked — ihre Deklaration ist erlaubt, aber nicht erforderlich, und wird normalerweise nicht gemacht. - Sie können mehr Typen deklarieren, als Sie tatsächlich werfen — nützlich, wenn Sie die Möglichkeit für zukünftige Implementierungen offen halten, obwohl es etwas unübersichtlich ist.
- Eine Methode, die eine andere überschreibt, kann gleiche oder weniger checked Exceptions deklarieren als die Elternklasse (und nur Subtypen der deklarierten). Sie kann keine neuen hinzufügen. Das ist das Liskov-Substitutionsprinzip auf Exceptions angewendet.
throw und throws zusammen
Eine echte Methode macht meist beides:
public User loadUser(String id) throws IOException {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("id must be non-blank");
}
String json = httpClient.get("/users/" + id); // may throw IOException
return parser.toUser(json);
}- Das
throws IOExceptiondeklariert die checked Exception, die vonhttpClient.getkommen kann. - Das
throw new IllegalArgumentException(...)löst eine unchecked Exception für ungültige Eingaben aus. Sie muss nicht in derthrows-Klausel erscheinen.
Eine Exception umhüllen
Wenn eine Low-Level-Exception auf Ihrer Ebene keine Bedeutung hat, umhüllen Sie sie in einer, die Bedeutung hat. Übergeben Sie das Original als Ursache, damit der Trace erhalten bleibt:
try {
return Files.readString(configPath);
} catch (IOException e) {
throw new ConfigLoadException("could not load config from " + configPath, e);
}Das Zwei-Argument-Konstruktor-Muster — (message, cause) — ist Standard bei Exception, IOException und allen eingebauten Typen. Wenn Sie Ihre eigene Exception-Klasse schreiben, geben Sie ihr beide Konstruktoren.
Ein ausgearbeitetes Beispiel
Ein kleines Banking-artiges Hilfsprogramm, das Eingaben mit IllegalArgumentException validiert, ein leeres Konto mit IllegalStateException signalisiert und eine checked Exception über throws an den Aufrufer weitergeben lässt. Der Treiber zeigt, wie jede einzelne beim Werfen aussieht.
Die drei Laufzeitfälle — Argument, Zustand und erfolgreiche Abhebung — fallen durch einen catch. Der vierte, archive(), kompiliert nur, weil main Exception abfangen darf und weil archive() throws ArchiveException deklariert hat. Versuchen Sie, die throws-Klausel zu entfernen, und das Programm kompiliert nicht mehr.
Was kommt als Nächstes
Der Compiler behandelt einige Exceptions streng (Sie müssen sie behandeln) und andere permissiv (das ist nicht erforderlich). Diese Unterscheidung ist das nächste Kapitel. Weiter zu Java checked vs. unchecked Exceptions.