← General program design CS Skill Tree

Exception handling

When a method cannot finish its job, it can stop and send its caller an object that describes the problem (an exception). Sending one is throwing it. Handling one is catching it.

In the practice exam, openInputFile and openOutputFile both catch a FileNotFoundException. The comment at the top of each file gives every message and the order of the checks, and the 2 methods handle a null name differently, so follow each file's own comment.

Checked and unchecked exceptions

GroupWhat the compiler requiresExamplesUsual cause
Checked: under Exception, outside RuntimeExceptionCatch it, or declare it with throws on the method header. Otherwise the method does not compile.FileNotFoundException, IOExceptionsomething outside the program, such as a missing file
Unchecked: under RuntimeExceptionNothing.IllegalArgumentException, NullPointerException, IndexOutOfBoundsExceptiona bug, such as a bad argument from the caller

The 2 constructors that open files both throw a checked exception. new Scanner(file) throws FileNotFoundException when the file is missing or cannot be read. new PrintStream(name) throws FileNotFoundException when the file cannot be created or opened, for example when the name is a folder.

Throwable
  |
  +-- Error              do not catch these
  |     +-- StackOverflowError
  |
  +-- Exception          checked, except the RuntimeException branch
        +-- IOException
        |     +-- FileNotFoundException
        |
        +-- RuntimeException     unchecked
              +-- IllegalArgumentException
              +-- NullPointerException
              +-- IndexOutOfBoundsException
                    +-- ArrayIndexOutOfBoundsException

A catch for a class also catches its subclasses. catch (IOException e) catches a FileNotFoundException too.

Catch or declare

A method with a line that can throw a checked exception has 2 choices.

// choice 1: catch it here
public static int countLines(String name) {
    File file = new File(name);
    Scanner in;
    try {
        in = new Scanner(file);
    } catch (FileNotFoundException e) {
        System.out.println("could not read " + name);
        return -1;
    }
    int count = 0;
    while (in.hasNextLine()) {
        in.nextLine();
        count++;
    }
    in.close();
    return count;
}

// choice 2: declare it, and the caller must catch or declare it
public static Scanner openScanner(String name) throws FileNotFoundException {
    File file = new File(name);
    return new Scanner(file);
}

How try and catch run

  1. Java runs the statements in the try block in order.
  2. If none of them throws, the catch block is skipped.
  3. If one throws, the rest of the try block is skipped, and the first catch block whose type matches the exception runs. If none matches, the exception passes to the caller.
  4. Either way, the method then continues after the catch block, unless that block returned.

The parentheses after catch declare a variable, the same way a method header declares a parameter. In catch (FileNotFoundException e), e is the exception object. e.getMessage() returns the text stored in it (for a missing file, something like gone.txt (No such file or directory)).

Turning a caught exception into a message

A catch block can build a message that tells the caller what went wrong. This method writes 1 line to a file and returns null when it works, or a message when the file cannot be opened.

public static String writeReport(String name, String line) {
    PrintStream out;
    try {
        out = new PrintStream(name);
    } catch (FileNotFoundException e) {
        return "report not written: " + e.getMessage();
    }
    out.println(line);
    out.close();
    return null;
}

Some messages include e.getMessage() and some do not. Build the message the comment shows, character for character, including spaces and punctuation. A test compares the whole String, so 1 extra space fails it. If a test still fails on a message, open the test file and compare your text with the String in its assertEquals line, because the test decides the score.

Throwing an exception yourself

A method throws when its caller passes a value it cannot use. Check the argument first, before the method changes anything, so a rejected call leaves the object as it was.

public void setQuantity(int quantity) {
    if (quantity < 0) {
        throw new IllegalArgumentException("quantity was negative: " + quantity);
    }
    this.quantity = quantity;
}

IllegalArgumentException is unchecked, so the header needs no throws. The message names the value that was wrong.

Checks in the order the instructions list

A method with several failure checks runs them top to bottom, and the first check that throws or returns ends the method. So the order of the checks sets which message the caller gets when 2 things are wrong at once.

Some checks also depend on an earlier one. name.equals("") on a null name throws NullPointerException, so the null check has to come before it. Write the checks in the order the comment lists them.

exists() and the try

new File(name) builds a path and does not touch the disk, so it never throws for a missing file. file.exists() asks the disk and returns true or false. A method can call exists() first to give a specific message for a missing file.

The method still needs the try around new Scanner(file). The compiler requires it, and a file can exist and still fail to open, for example a folder or a file the account may not read.

An empty catch block

An empty catch block compiles. The program then keeps running with a Scanner or PrintStream that was never opened, and a later line fails with a NullPointerException far from the cause. A catch block should record the problem, fix it, or stop the method.

Catching Exception in place of FileNotFoundException also catches bugs you did not expect, such as a NullPointerException. A try around the whole method body also catches the IllegalArgumentException your own check throws, so the caller never sees it. Keep the try around the line that opens the file.

What an uncaught exception does

An exception that nothing catches passes to the caller, then to that method's caller, and so on. Each method it passes through stops at the line of the call. If it reaches main uncaught, the program stops and prints a stack trace (the list of method calls that were running, newest first). The first line names the exception and its message. Each line after it gives a method and a line number.

The finally block

A finally block after try and catch runs on every way out of the try: a normal finish, a caught exception, an uncaught exception, or a return. It holds cleanup, such as closing a file.

Scanner in = null;
try {
    in = new Scanner(file);
    // read from it
} catch (FileNotFoundException e) {
    System.out.println("could not open " + file.getName());
} finally {
    if (in != null) {
        in.close();
    }
}

If the constructor threw, in is still null when finally runs, and in.close() would throw a NullPointerException. The if prevents that.

Readiness check

Work each one on paper and write the answer down before you open the answers.

TraceWhich lines run
public static int lengthOf(String s) {
    System.out.println("A");
    if (s == null) {
        throw new IllegalArgumentException("s was null");
    }
    System.out.println("B");
    return s.length();
}
  1. lengthOf("hi"). What prints, and what comes back?
  2. lengthOf(null), called from a main with no try. What prints, and what does the program do next?
  3. The same call, from a main that wraps it in a try with catch (IllegalArgumentException e) printing e.getMessage(). What prints?
  4. Does the header of lengthOf need a throws clause?
Answers

1. A, then B. It returns 2.

2. A prints and B does not. The throw ends the method, nothing catches the exception, and the program stops and prints a stack trace.

3. A, then s was null. The catch block runs, and main continues after it.

4. No. IllegalArgumentException is unchecked.

TraceWill it compile

For each one, say whether it compiles. If not, give the 2 fixes.

  1. A method whose body is Scanner in = new Scanner(new File("data.txt"));, with nothing on the header.
  2. The same line inside a try whose catch names IOException.
  3. The same line, with no try, in a method whose header ends throws FileNotFoundException.
  4. A method that checks an argument and throws IllegalArgumentException, with nothing on the header.
Answers

1. It does not compile. FileNotFoundException is checked, and the method neither catches nor declares it. Fix it with a try and catch around the line, or with throws FileNotFoundException on the header.

2. It compiles. FileNotFoundException is a subclass of IOException.

3. It compiles. The caller must now catch or declare the exception.

4. It compiles. An unchecked exception needs no declaration.

TraceWhich message
public static String check(String name) {
    if (name == null) {
        return "no name";
    }
    if (name.equals("")) {
        return "empty name";
    }
    File file = new File(name);
    if (!file.exists()) {
        return "no such file";
    }
    return "ok";
}

notes.txt is on the disk and gone.txt is not.

  1. What does each call return: check(null), check(""), check("gone.txt"), check("notes.txt")?
  2. The first 2 checks swap places. What does check(null) do now?
  3. Does new File("gone.txt") throw?
Answers

1. no name, empty name, no such file, ok.

2. It throws NullPointerException on the line name.equals(""), because that line now runs before the null check.

3. No. A File is only a path. exists() is the line that asks the disk.

Further reading