Chapter 4 · Robust, modern Java
Exceptions
Step 1 of 22 · Concept
When things go wrong
In lesson 4 a division by zero stopped a program with an exception. An exception does not have to end the program.
In this lesson you will:
- catch an exception and carry on,
- throw one from a method that checks its input,
- write an exception class of your own.
Step 2 of 22 · Concept
Text that is not a number
Integer.parseInt(text) turns text into an int. When the text is not a whole number, it throws an
exception: it stops and reports the problem.
| call | gives |
|---|---|
Integer.parseInt("42") | the int 42 |
Integer.parseInt("-7") | the int -7 |
Integer.parseInt("forty") | a NumberFormatException |
Integer.parseInt("4.5") | a NumberFormatException: not a whole number |
People often type something that is not a number. A program that reads numbers needs a plan for that.
Step 3 of 22 · Concept
try and catch
- Java runs the
tryblock line by line. - When a line throws, the rest of the
tryblock is skipped. - The
catchblock for that kind of exception runs instead. - Then the program goes on after both.
Step 4 of 22 · Quiz
What will it print?
Type exactly what this program prints.
try {
System.out.println("A");
int n = Integer.parseInt("forty");
System.out.println("B");
} catch (NumberFormatException e) {
System.out.println("C");
}
System.out.println("D");
Step 5 of 22 · Demo
See it run
Run it and compare with your answer. Then change "forty" to "40" and run it again: now B prints and the
catch block is skipped.
Step 6 of 22 · Demo
The exception is an object
e is the exception that was caught: an object, made when parseInt threw. e.getMessage() gives its message.
This loop adds up the texts that are numbers and skips the others. Run it.
Step 7 of 22 · Exercise
Your turn: a number or a fallback
Write the method static int parseOr(String text, int fallback). It returns the number in text, or fallback
when text is not a whole number. The program must print:
12 -1 0
A return can stand inside a try block, and inside a catch block too.
In the try block, return what Integer.parseInt(text) gives. In catch (NumberFormatException e), return
fallback.
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
Step 8 of 22 · Concept
finally runs in every case
A finally block comes after the last catch. It runs whichever way the try ends. It runs even after a
return, and even when an exception leaves the method.
It is the place for clean-up that must always happen, such as closing a file the method opened.
Step 9 of 22 · Quiz
What will it print?
main calls check("5"), then check("five"). Type exactly what the program prints.
static void check(String text) {
try {
int n = Integer.parseInt(text);
System.out.println("number " + n);
} catch (NumberFormatException e) {
System.out.println("not a number");
} finally {
System.out.println("done with " + text);
}
}
Step 10 of 22 · Demo
See it run
Run it and compare with your answer.
Step 11 of 22 · Concept
A method can refuse
Lesson 7's withdraw quietly did nothing when the balance was too small. A method can refuse bad input instead,
with throw:
void withdraw(int amount) {
if (amount > balance) {
throw new IllegalArgumentException(
"only " + balance + " in the account");
}
balance = balance - amount;
}
new IllegalArgumentException("…")makes an exception object with a message.throwends the method at once. The lines after it do not run.- The exception goes to the caller, as if the call itself had thrown.
Step 12 of 22 · Trace
An exception leaves frame after frame
main calls buy, and buy calls pay, which throws. Press Next and watch the stack.
static int pay(int coins, int price) {
if (price > coins) {
throw new IllegalArgumentException("too little");
}
return coins - price;
}
static void buy(int coins) {
int left = pay(coins, 80);
System.out.println("left " + left);
}
public static void main(String[] args) {
try {
buy(50);
} catch (IllegalArgumentException e) {
System.out.println("Refused");
}
System.out.println("End");
}mainis in itstryblock. Line 15 is next: it callsbuy(50).- A frame for
buygoes on the stack, withcoins50. Line 9 callspay. - A frame for
paygoes on top. 80 is more than 50, so line 3 is next: it throws. pay's frame is removed at once. The exception arrives inbuyat line 9, soleftis never set.buyhas nocatch, so its frame is removed too: line 10 never runs. Thecatchofmainmatches: line 17 is next.- The
catchblock printedRefused. The program goes on after thetry, at line 19. - Line 19 printed
End, andmainis over.
Step 13 of 22 · Quiz
What happens to buy?
pay throws, and buy has no catch. What happens to the line System.out.println("left " + left); in buy?
static int pay(int coins, int price) {
if (price > coins) {
throw new IllegalArgumentException("too little");
}
return coins - price;
}
static void buy(int coins) {
int left = pay(coins, 80);
System.out.println("left " + left);
}
public static void main(String[] args) {
try {
buy(50);
} catch (IllegalArgumentException e) {
System.out.println("Refused");
}
System.out.println("End");
}Step 14 of 22 · Demo
A method that refuses
This Account.java also refuses a negative amount. Run it.
The first refusal is caught. The second is not: it ends the program with a stack trace and exit code 1.
Step 15 of 22 · Exercise
Your turn: a deposit that refuses
Change deposit in Account.java. For an amount of 0 or less, it throws an IllegalArgumentException with the
message not a deposit: and the amount. The program must print:
Refused: not a deposit: 0
Refused: not a deposit: -3
Grace: 35
withdraw in the same file refuses in the same way. The check goes first, before balance changes.
if (amount <= 0), and inside it throw new IllegalArgumentException(…) with the message
"not a deposit: " + amount.
if (amount <= 0) {
throw new IllegalArgumentException(
"not a deposit: " + amount);
}
Step 16 of 22 · Demo
A catch block can throw
A catch block may throw an exception of its own. Here amount catches the exception of parseInt and throws a
new one, with a message that says what went wrong.
Run it. main catches the new exception and prints its message.
Step 17 of 22 · Concept
Two kinds of exception
- Unchecked exceptions extend
RuntimeException. javac lets you call without atry: a careful caller never passes a bad amount. - Checked exceptions are the others. They are for failures a caller cannot rule out, such as a sold-out concert. javac makes the caller deal with them.
Step 18 of 22 · Concept
throws: a warning in the header
A method that may throw a checked exception says so in its header, with throws:
static int book(int left, int seats)
throws SoldOutException {
Every caller must then do one of two things:
- catch the exception with
tryandcatch, - or say
throws SoldOutExceptionin its own header.
Otherwise javac stops with unreported exception.
Step 19 of 22 · Try it
Try it: javac insists
SoldOutException extends Exception, so it is checked. Its constructor passes the message to super, as in
lesson 7.
This book throws one, but its header does not say so. Run it and read javac's error. Then add throws
SoldOutException to the header of book.
throws SoldOutException goes after the parameters' ) and before the {.
Step 20 of 22 · Check yourself
Check yourself: a wallet
Main.java uses a Wallet and its exception. Write the class NotEnoughMoneyException, a checked exception
with a message.
Then write the two methods of Wallet, as their comments say. The program must print:
not a number: ten
not positive: -5
coins: 70
paid 30, coins: 40
refused: need 20 more
coins: 40
In insert, catch the NumberFormatException of parseInt, and throw your own IllegalArgumentException from
that catch block.
Step 21 of 22 · Challenge · optional
Challenge: two kinds of trouble
Optional. It earns the lesson's third star.
Menu looks up a price by its number. It skips text that is not a number, but crashes on "9": there is no
item 9.
Make it print no such item for both. The output must be:
price 7
no such item: x
no such item: 9
Run it and read the stack trace: which exception does "9" throw, and on which line?
One try may have several catch blocks, one after another. Java runs the first that matches.
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("no such item: " + pick);
}
Step 22 of 22 · Recap
Recap
- A method throws an exception to say it cannot go on.
tryruns code that may throw. A matchingcatchruns instead of the rest.finallyalways runs.- An exception leaves method after method, removing their frames, until a
catchtakes it, or ends the program. throw new IllegalArgumentException("…")refuses bad input.- A checked exception must be caught or declared with
throws. Your own extendsException.
Next: lesson 15 describes what several classes can all do, with an interface.
Review
3 questions from this lesson, in a new order each time. Answer from memory: 2 of 3 right on the first try passes. On a day after you finish the lesson, a pass earns 15 XP.
The review needs JavaScript.
public class Catch {
public static void main(String[] args) {
try {
System.out.println("A");
int n = Integer.parseInt("forty");
System.out.println("B");
} catch (NumberFormatException e) {
System.out.println("C");
}
System.out.println("D");
}
}
public class Parse {
public static void main(String[] args) {
String[] inputs = {"42", "forty", "7"};
int total = 0;
for (String text : inputs) {
try {
total += Integer.parseInt(text);
System.out.println("read " + text);
} catch (NumberFormatException e) {
String why = e.getMessage();
System.out.println("skipped: " + why);
}
}
System.out.println("total " + total);
}
}
public class ParseOr {
// write parseOr here
public static void main(String[] args) {
System.out.print(parseOr("12", 0) + " ");
System.out.print(parseOr("twelve", -1) + " ");
System.out.println(parseOr("", 0));
}
}
public class Finally {
static void check(String text) {
try {
int n = Integer.parseInt(text);
System.out.println("number " + n);
} catch (NumberFormatException e) {
System.out.println("not a number");
} finally {
System.out.println("done with " + text);
}
}
public static void main(String[] args) {
check("5");
check("five");
}
}
public class Main {
public static void main(String[] args) {
Account a = new Account("Ada", 50);
try {
a.withdraw(80);
} catch (IllegalArgumentException e) {
System.out.println("Refused: " + e.getMessage());
}
System.out.println(a);
a.withdraw(-5);
System.out.println("never printed");
}
}
public class Main {
public static void main(String[] args) {
Account a = new Account("Grace", 10);
int[] amounts = {5, 0, -3, 20};
for (int amount : amounts) {
try {
a.deposit(amount);
} catch (IllegalArgumentException e) {
String why = e.getMessage();
System.out.println("Refused: " + why);
}
}
System.out.println(a);
}
}
public class Amount {
static int amount(String text) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"not an amount: " + text);
}
}
public static void main(String[] args) {
String[] inputs = {"30", "thirty"};
for (String text : inputs) {
try {
System.out.println("amount " + amount(text));
} catch (IllegalArgumentException e) {
String why = e.getMessage();
System.out.println("Refused: " + why);
}
}
}
}
public class Tickets {
static int book(int left, int seats) {
if (seats > left) {
throw new SoldOutException(
"only " + left + " seats left");
}
return left - seats;
}
public static void main(String[] args) {
int left = 5;
try {
left = book(left, 3);
System.out.println("booked 3, " + left + " left");
left = book(left, 3);
System.out.println("booked 3 more");
} catch (SoldOutException e) {
System.out.println("Sorry: " + e.getMessage());
}
}
}
public class Main {
public static void main(String[] args) {
Wallet w = new Wallet();
String[] inputs = {"50", "ten", "-5", "20"};
for (String text : inputs) {
try {
w.insert(text);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
System.out.println("coins: " + w.coins());
try {
w.pay(30);
System.out.println("paid 30, coins: " + w.coins());
w.pay(60);
System.out.println("paid 60");
} catch (NotEnoughMoneyException e) {
System.out.println("refused: " + e.getMessage());
} finally {
System.out.println("coins: " + w.coins());
}
}
}
public class Menu {
public static void main(String[] args) {
int[] prices = {5, 8, 2, 7};
String[] picks = {"3", "x", "9"};
for (String pick : picks) {
try {
int i = Integer.parseInt(pick);
System.out.println("price " + prices[i]);
} catch (NumberFormatException e) {
System.out.println("no such item: " + pick);
}
}
}
}