W3docs

Java Exceptions

Ein Überblick über Exceptions in Java — was sie sind, die Exception-Hierarchie und warum Exception-Handling wichtig ist.

Eine Exception ist das, was passiert, wenn Ihr Programm auf eine Situation trifft, die es auf seinem aktuellen Pfad nicht verarbeiten kann — eine Datei, die nicht vorhanden ist, eine durch null dividierte Zahl, ein Array-Index, der außerhalb der Grenzen liegt. Anstatt eine falsche Antwort zurückzugeben oder den Zustand stillschweigend zu beschädigen, stoppt Java die aktuelle Methode, erstellt ein Exception-Objekt, das beschreibt, was falsch gelaufen ist, und beginnt, nach Code zu suchen, der damit umgehen kann. Das Exception-Mechanismus zu erlernen bedeutet zu lernen, wie Java Ihnen mitteilt, was falsch gelaufen ist, wo und was Sie dagegen tun könnten.

Was eine Exception tatsächlich ist

Eine Exception ist ein normales Java-Objekt. Konkret eine Instanz einer Klasse, die von java.lang.Throwable erbt. Wenn etwas schief geht, erstellt die JVM (oder Ihr eigener Code) eines dieser Objekte und wirft es. Von diesem Moment an folgt das Programm einem anderen Kontrollfluss, bis entweder ein catch-Block die Exception akzeptiert oder der Thread beendet wird.

Das Objekt trägt Informationen: einen Typ (die Klasse — NullPointerException, IOException usw.), eine Nachricht (eine menschenlesbare Beschreibung) und einen Stack Trace (die Aufrufkette, die im Moment der Erstellung der Exception eingefroren wurde). Wenn Sie eine Exception in einer Konsole lesen, lesen Sie diese drei Dinge.

Exception in thread "main" java.lang.ArithmeticException: / by zero
  at calc.Money.divide(Money.java:42)
  at calc.App.main(App.java:11)

Die erste Zeile ist Typ + Nachricht. Die eingerückten Zeilen sind der Stack Trace, neuester Aufruf zuerst.

Warum Exceptions statt Fehlercodes

Ältere Sprachen — C ist das klassische Beispiel — signalisieren Fehler mit Rückgabecodes: Jede Funktion gibt ein int zurück, Sie prüfen es, und wenn es negativ ist, ist etwas schief gelaufen. Dieser Ansatz hat zwei Probleme:

  1. Sie können den Rückgabewert ignorieren. Ein Aufrufer, der das Prüfen vergisst, sieht keinen unmittelbaren Fehler, und der Bug taucht später an einem verwirrenden Ort auf.
  2. Der Fehlerpfad verstopft den Happy Path. Jede Zeile echter Arbeit ist in if (err) return err; eingebettet.

Exceptions kehren das um. Der Standard ist, dass eine nicht behandelte Exception die Ausführung stoppt, und zwar lautstark. Sie entscheiden sich für die Behandlung dort, wo Sie tatsächlich eine Strategie haben. Der gute Pfad bleibt sauber; der Wiederherstellungspfad befindet sich in seinem eigenen Block.

Die drei Dinge, die schief gehen können

Java sortiert alles unter Throwable in drei Kategorien, und der Unterschied ist wichtig, da die Sprache sie unterschiedlich behandelt:

  • Error — die JVM selbst hat Probleme. OutOfMemoryError, StackOverflowError. Diese fangen Sie im normalen Code nicht ab. Es gibt normalerweise nichts Sinnvolles, was Sie tun können.
  • RuntimeException (eine Unterklasse von Exception) — Programmierfehler, die zur Laufzeit auftreten. NullPointerException, IndexOutOfBoundsException, ClassCastException. Der Compiler zwingt Sie nicht, sie zu behandeln, weil sie in korrektem Code nicht auftreten sollten.
  • Checked Exception (alles andere unter Exception) — behebbare Fehler, die das Programm antizipieren sollte. IOException, SQLException. Der Compiler verlangt, dass Sie sie entweder abfangen oder sie in der throws-Klausel Ihrer Methode deklarieren.

Die Grenze zwischen "Programmierfehler" (Runtime-Exception) und "erwarteter Fehler" (Checked Exception) ist eine der am meisten diskutierten Designentscheidungen in Java. Wir kommen darauf zurück unter Java checked vs. unchecked exceptions.

Wie Werfen und Fangen funktionieren

Wenn ein throw ausgeführt wird, tut die JVM Folgendes:

  1. Entfaltet den Stack — die aktuelle Methode wird abrupt beendet, dann ihr Aufrufer genauso, und so weiter.
  2. An jedem Frame prüft sie auf einen try { ... } catch (SomeType e) { ... }-Block, dessen catch zum Typ der Exception passt (oder einem Supertyp).
  3. Der erste passende catch gewinnt. Die Kontrolle springt dorthin, der Stack hört auf sich zu entfalten, und die Ausführung setzt im catch-Block fort.
  4. Wenn nichts passt, stirbt der Thread. In einem Single-Thread-Programm bedeutet das, dass die JVM den Stack Trace ausgibt und beendet wird.

Deshalb kann eine geworfene Exception viele Methoden durchlaufen, bevor sie gefangen wird — jeder nicht abgefangene Wurf steigt einen weiteren Frame auf.

Ein erster Eindruck

Sie müssen kein throw schreiben, um auf eine Exception zu treffen — Java selbst wirft sie, wenn etwas schief geht. Hier ist das einfachste Beispiel: Eine ganze Zahl durch null dividieren. Die JVM erstellt eine ArithmeticException für Sie.

java— editable, runs on the server

Ohne das try/catch würde die dritte Iteration das gesamte Programm zum Absturz bringen und die vierte würde nie ausgeführt werden. Mit ihm ist der Fehler eingedämmt: Wir erhalten eine Fehlermeldung und die Schleife läuft weiter. Diese Fähigkeit — den Schaden eines Fehlers zu begrenzen — ist der eigentliche Zweck des Exception-Mechanismus.

Was dieser Teil des Buches behandelt

Die verbleibenden Kapitel in diesem Teil sind das Arbeitsvokabular des Java-Exception-Handlings, Stück für Stück:

  • Die Form von try/catch/finally und wofür jede Klausel ist.
  • Mehrere catches und die Multi-catch-Abkürzung.
  • try-with-resources für alles, was geschlossen werden muss.
  • Selbst Exceptions werfen mit throw und sie mit throws deklarieren.
  • Checked vs. unchecked: Welche wann verwendet werden soll.
  • Die vollständige Klassenhierarchie.
  • Eigene Exception-Typen für Ihre Domäne schreiben.
  • Die Prinzipien, die gutes Exception-Handling von defensivem Rauschen unterscheiden.

Lesen Sie es der Reihe nach — jedes Kapitel setzt das vorherige voraus.

Was als Nächstes kommt

Exception-Handling beginnt mit dem grundlegendsten Konstrukt: dem try/catch-Block. Weiter zu Java try...catch.

Practice

Übung
Eine Methode `loadConfig()` liest eine Datei und wirft bei einem Fehler `IOException`. Der Aufrufer umschließt den Aufruf mit `try { loadConfig(); } catch (RuntimeException e) { ... }`. Das IO schlägt fehl. Was passiert?
Eine Methode `loadConfig()` liest eine Datei und wirft bei einem Fehler `IOException`. Der Aufrufer umschließt den Aufruf mit `try { loadConfig(); } catch (RuntimeException e) { ... }`. Das IO schlägt fehl. Was passiert?
Was this page helpful?