jario_
Wróć do przeglądu

Rozdział 10

Ausnahmen und Enums

Bisher brach ein Programm einfach ab, wenn etwas schiefging, etwa bei Integer.parseInt("abc"). Mit Ausnahmen (Exceptions) fängst du solche Fehler ab und reagierst darauf, oder du meldest selbst einen Fehler. Dazu kommen Enums: Typen mit einer festen Liste von Werten, wie die Phasen einer Ampel.

Ausnahmen abfangen

Code, der schiefgehen kann, steht in einem try-Block. Fliegt dort eine Ausnahme, springt Java sofort in den passenden catch-Block. Der Rest des try-Blocks wird übersprungen. Danach geht es normal weiter.

try {
    int value = Integer.parseInt(text);
} catch (NumberFormatException e) {
    // text was not a number
}

e.getMessage() liefert die Fehlermeldung. Ein finally-Block läuft immer, ob es geklappt hat oder nicht, sogar wenn im try ein return steht. Dort gehört hin, was auf jeden Fall passieren muss, zum Beispiel eine Verbindung schließen.

public class Main {
    public static void main(String[] args) {
        String[] inputs = {"42", "abc", "7"};
        int sum = 0;
        for (String input : inputs) {
            try {
                int value = Integer.parseInt(input);
                sum += value;
                System.out.println("Added " + value);
            } catch (NumberFormatException e) {
                System.out.println("Not a number: " + input);
            } finally {
                System.out.println("Checked " + input);
            }
        }
        System.out.println("Sum: " + sum);
    }
}

Ausnahmen werfen und eigene Exceptions

Mit throw new IllegalArgumentException("No values"); meldest du selbst einen Fehler. Die Methode endet an dieser Stelle, der Aufrufer bekommt die Ausnahme.

Eine eigene Exception ist eine Klasse, die von Exception erbt. Die Meldung gibt ihr Konstruktor mit super(...) weiter. Es gibt zwei Sorten:

  • Checked: alles, was von Exception erbt. Der Compiler verlangt, dass du sie abfängst oder mit throws im Methodenkopf ankündigst: void buyApple() throws OutOfStockException.
  • Unchecked: alles, was von RuntimeException erbt, z. B. IllegalArgumentException und NumberFormatException. Hier verlangt der Compiler nichts.

Ein throws reicht die Ausnahme an den Aufrufer weiter. Dann muss sich der darum kümmern.

class OutOfStockException extends Exception {
    public OutOfStockException(String product) {
        super(product + " is out of stock");
    }
}

class Shop {
    private int apples = 2;

    public void buyApple() throws OutOfStockException {
        if (apples == 0) {
            throw new OutOfStockException("Apple");
        }
        apples--;
    }
}

public class Main {
    public static double divide(double a, double b) {
        if (b == 0) {
            throw new IllegalArgumentException("Cannot divide by 0");
        }
        return a / b;
    }

    public static void main(String[] args) {
        try {
            System.out.println(divide(10, 4));
            System.out.println(divide(1, 0));
            System.out.println("This line never runs");
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }

        Shop shop = new Shop();
        for (int i = 1; i <= 3; i++) {
            try {
                shop.buyApple();
                System.out.println("Bought apple " + i);
            } catch (OutOfStockException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

Enums

enum Season { SPRING, SUMMER, AUTUMN, WINTER } legt einen Typ mit genau vier Werten an. Eine Variable vom Typ Season kann nichts anderes enthalten, Tippfehler meldet der Compiler.

  • Enum-Werte vergleichst du mit ==.
  • values() liefert alle Werte als Array, in der Reihenfolge der Deklaration. ordinal() ist die Position, ab 0.
  • Als Text ergibt ein Wert seinen Namen, z. B. SUMMER.
  • Im switch schreibst du die Werte ohne Klassennamen: case SUMMER ->.

Ein Enum kann wie eine Klasse Attribute, einen Konstruktor und Methoden haben. Die Werte in Klammern hinter jeder Konstante gehen an den Konstruktor: EURO(100).

enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}

enum Coin {
    CENT(1), TEN_CENTS(10), EURO(100);

    private final int cents;

    Coin(int cents) {
        this.cents = cents;
    }

    public int getCents() {
        return cents;
    }
}

public class Main {
    public static void main(String[] args) {
        Season now = Season.SUMMER;
        System.out.println(now);
        System.out.println("Position: " + now.ordinal());
        System.out.println(now == Season.SUMMER);

        for (Season s : Season.values()) {
            System.out.print(s + " ");
        }
        System.out.println();

        String advice = switch (now) {
            case SUMMER -> "Sunscreen";
            case WINTER -> "Gloves";
            default -> "Umbrella";
        };
        System.out.println(advice);

        int total = 0;
        for (Coin c : Coin.values()) {
            total += c.getCents();
        }
        System.out.println("All coins: " + total + " cents");
    }
}

Typowe błędy

  • Checked Exception nicht behandelt

    Rufst du eine Methode mit throws OutOfStockException auf, ohne try/catch und ohne eigenes throws, meldet der Compiler unreported exception OutOfStockException; must be caught or declared to be thrown.

  • Falsche Reihenfolge der catch-Blöcke

    Java nimmt den ersten passenden catch. Steht catch (Exception e) vor catch (NumberFormatException e), kann der zweite nie greifen. Der Compiler meldet exception NumberFormatException has already been caught. Die speziellere Ausnahme gehört nach oben.

  • Leerer catch-Block

    catch (Exception e) { } verschluckt jeden Fehler, auch solche, mit denen du nicht gerechnet hast. Fang nur die Ausnahme ab, die du erwartest, und reagiere sichtbar darauf.

  • Durch 0 teilen

    Bei int wirft 10 / 0 eine ArithmeticException. Bei double gibt es keine Ausnahme: 10.0 / 0 ergibt Infinity. Deshalb prüft divide im Beispiel selbst und wirft eine Ausnahme.