EMZETT.
Login

Exceptions

Kurz: Laufzeitfehler, die mit try/catch gezielt abgefangen und behandelt werden können, statt das ganze Programm abstürzen zu lassen.

Genauer: Java unterscheidet geprüfte Exceptions (checked, z. B. IOException — der Compiler zwingt zum Abfangen oder Weiterreichen) und ungeprüfte (unchecked, z. B. NullPointerException — Compiler prüft nichts, meist Programmierfehler statt erwartbarer Zustand). Ein finally-Block läuft immer, egal ob eine Exception geworfen wurde oder nicht — typisch für Aufräumarbeiten.

try {
    int ergebnis = 10 / zahl;
} catch (ArithmeticException e) {
    System.out.println("Division durch Null!");
}

Im Detail

class NichtGenugGeldException extends Exception { // checked - eigene Exception-Klasse
    public NichtGenugGeldException(String nachricht) {
        super(nachricht);
    }
}
 
class Konto {
    private double saldo = 100.0;
 
    // "throws" zwingt Aufrufer, die Exception zu behandeln oder weiterzureichen
    void abheben(double betrag) throws NichtGenugGeldException {
        if (betrag > saldo) {
            throw new NichtGenugGeldException("Nur " + saldo + "€ verfügbar");
        }
        saldo -= betrag;
    }
}
 
Konto k = new Konto();
try {
    k.abheben(150.0);
} catch (NichtGenugGeldException e) {
    System.out.println("Fehler: " + e.getMessage());
} finally {
    System.out.println("Läuft immer, egal ob Exception oder nicht");
}

Checked Exceptions (Unterklassen von Exception, aber nicht von RuntimeException) zwingen den Compiler-Check: jede Methode, die eine checked Exception werfen kann, muss das per throws deklarieren, und jeder Aufrufer muss sie entweder abfangen oder selbst weiterreichen — sonst kompiliert der Code nicht. Unchecked Exceptions (Unterklassen von RuntimeException, z. B. NullPointerException) verlangen das nicht, weil sie meist Programmierfehler statt erwartbare Zustände darstellen, die theoretisch überall auftreten könnten. Eigene fachliche Fehler (wie NichtGenugGeldException oben) modelliert man üblicherweise als checked Exception, wenn der Aufrufer den Fehler sinnvoll behandeln kann und soll — bei reinen Programmierfehlern (falsche Nutzung einer API) ist RuntimeException üblicher.

Die Exception-Kette (caused by)

Beim Abfangen einer Exception lässt sich eine NEUE, aussagekräftigere Exception werfen, ohne die ursprüngliche Ursache zu verlieren — der zweite Konstruktor-Parameter (cause) verkettet beide, sodass der Stacktrace am Ende beide Ebenen zeigt (“Caused by: …”):

try {
    ladeKonfiguration();
} catch (IOException e) {
    throw new RuntimeException("Konfiguration konnte nicht geladen werden", e); // e als Ursache verkettet
}

Ohne diese Verkettung ginge die ursprüngliche technische Ursache (z. B. “Datei nicht gefunden”) verloren — im Log stünde nur die neue, allgemeinere Fehlermeldung, was die Fehlersuche erheblich erschwert.

Best Practice: nicht schlucken

Ein häufiger Anfängerfehler ist ein leerer catch-Block, der eine Exception komplett “verschluckt”:

try {
    riskanteOperation();
} catch (Exception e) {
    // NICHTS - der Fehler verschwindet spurlos, das Programm läuft scheinbar normal weiter
}

Das führt zu besonders schwer auffindbaren Bugs, da das Programm nicht abstürzt, aber auch nicht das erwartete Ergebnis liefert. Mindestens ein Log-Eintrag (e.printStackTrace() oder besser ein echtes Logging-Framework) sollte in jedem catch-Block stehen, selbst wenn der Fehler bewusst ignoriert werden soll.

Siehe auch: Multiple Exceptions, try-with-resources, Errors