W3docs

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 Throwable werfen. Der Compiler erzwingt dies — throw "oops"; kompiliert nicht.
  • Sie werfen immer eine Instanz, keine Klasse. throw new X(...), niemals throw 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 ArgumenteIllegalArgumentException für „Sie haben mich falsch aufgerufen."
  • Falscher ZustandIllegalStateException fü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 Argument
  • IllegalStateException — falscher Zustand
  • NullPointerException — ein erforderliches Argument war null (verwenden Sie Objects.requireNonNull)
  • UnsupportedOperationException — Operation nicht implementiert (z. B. add einer 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 IOException deklariert die checked Exception, die von httpClient.get kommen kann.
  • Das throw new IllegalArgumentException(...) löst eine unchecked Exception für ungültige Eingaben aus. Sie muss nicht in der throws-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.

java— editable, runs on the server

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.

Übungen

Übung
Eine Methodensignatur lautet `public void save() throws IOException`. Der Methodenrumpf ist leer — er wirft nichts. Wird es kompilieren?
Eine Methodensignatur lautet `public void save() throws IOException`. Der Methodenrumpf ist leer — er wirft nichts. Wird es kompilieren?
Was this page helpful?