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
| Group | What the compiler requires | Examples | Usual cause |
|---|---|---|---|
Checked: under Exception, outside RuntimeException | Catch it, or declare it with throws on the method header. Otherwise the method does not compile. | FileNotFoundException, IOException | something outside the program, such as a missing file |
Unchecked: under RuntimeException | Nothing. | IllegalArgumentException, NullPointerException, IndexOutOfBoundsException | a 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
- Java runs the statements in the
tryblock in order. - If none of them throws, the
catchblock is skipped. - If one throws, the rest of the
tryblock is skipped, and the firstcatchblock whose type matches the exception runs. If none matches, the exception passes to the caller. - Either way, the method then continues after the
catchblock, 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 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.
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();
}
lengthOf("hi"). What prints, and what comes back?lengthOf(null), called from amainwith notry. What prints, and what does the program do next?- The same call, from a
mainthat wraps it in atrywithcatch (IllegalArgumentException e)printinge.getMessage(). What prints? - Does the header of
lengthOfneed athrowsclause?
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.
For each one, say whether it compiles. If not, give the 2 fixes.
- A method whose body is
Scanner in = new Scanner(new File("data.txt"));, with nothing on the header. - The same line inside a
trywhosecatchnamesIOException. - The same line, with no
try, in a method whose header endsthrows FileNotFoundException. - 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.
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.
- What does each call return:
check(null),check(""),check("gone.txt"),check("notes.txt")? - The first 2 checks swap places. What does
check(null)do now? - 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
- Lesson: Exceptions (The Java Tutorials). Oracle's full tutorial on exceptions.
- The Catch or Specify Requirement (The Java Tutorials). The rule for checked exceptions, and which kinds are exempt.
- The finally Block (The Java Tutorials). Every path a
finallyblock runs on. - Unchecked Exceptions, from the same trail. Where the line between the 2 groups sits, and why.
- Throwable (Java SE 19 & JDK 19). The root class, where
getMessageis defined. - IllegalArgumentException (Java SE 19 & JDK 19). The usual type for an argument a method cannot use.
- FileNotFoundException (Java SE 19 & JDK 19). The checked exception the file constructors throw.
- Chapter 11. Exceptions, from the Java Language Specification. The exact rules.
- Reading 6: Specifications, MIT 6.031, Software Construction. Written in Java. Covers what a method promises, including what it throws.
- Java also has a
trythat opens a stream in its parentheses and closes it at the end: The try-with-resources Statement. It does not fit a stream kept in a field and closed by a separate method.