jario_
Back to overview

Chapter 10

Exceptions and enums

So far, a program simply stopped when something went wrong, for example with Integer.parseInt("abc"). With exceptions you catch such errors and react to them, or report an error yourself. On top of that come enums: types with a fixed list of values, like the phases of a traffic light.

Catching exceptions

Code that can go wrong goes in a try block. If an exception is thrown there, Java jumps straight to the matching catch block. The rest of the try block is skipped. After that, the program continues normally.

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

e.getMessage() returns the error message. A finally block always runs, whether it worked or not, even if there's a return in the try. Put there what must happen in any case, such as closing a connection.

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);
    }
}

Throwing exceptions and your own exceptions

With throw new IllegalArgumentException("No values"); you report an error yourself. The method ends at that point, and the caller gets the exception.

Your own exception is a class that extends Exception. Its constructor passes the message on with super(...). There are two kinds:

  • Checked: everything that extends Exception. The compiler requires you to catch it or announce it with throws in the method header: void buyApple() throws OutOfStockException.
  • Unchecked: everything that extends RuntimeException, e.g. IllegalArgumentException and NumberFormatException. Here the compiler requires nothing.

A throws passes the exception on to the caller, who then has to deal with it.

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 } creates a type with exactly four values. A variable of type Season can't hold anything else; the compiler catches typos.

  • You compare enum values with ==.
  • values() returns all values as an array, in the order they are declared. ordinal() is the position, from 0.
  • As text, a value gives its name, e.g. SUMMER.
  • In a switch, you write the values without the class name: case SUMMER ->.

Like a class, an enum can have fields, a constructor and methods. The values in parentheses after each constant go to the constructor: 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");
    }
}

Common mistakes

  • Checked exception not handled

    If you call a method with throws OutOfStockException without try/catch and without your own throws, the compiler reports unreported exception OutOfStockException; must be caught or declared to be thrown.

  • Wrong order of catch blocks

    Java takes the first matching catch. If catch (Exception e) comes before catch (NumberFormatException e), the second can never apply. The compiler reports exception NumberFormatException has already been caught. The more specific exception goes first.

  • Empty catch block

    catch (Exception e) { } swallows every error, including ones you didn't expect. Only catch the exception you expect, and react to it visibly.

  • Dividing by 0

    With int, 10 / 0 throws an ArithmeticException. With double there's no exception: 10.0 / 0 gives Infinity. That's why divide in the example checks itself and throws an exception.