Throwing and your own exceptions
Problem
The ATM says "take your money"
The account keeps its balance. If the amount to withdraw is negative or there is not enough money, the method simply does nothing:
public void withdraw(int amount) { if (amount > 0 && amount <= balance) { balance -= amount; }}The ATM uses it like this:
Account account = new Account(100);account.withdraw(500);System.out.println("Take your money.");System.out.println("Balance: €" + account.getBalance());Take your money.Balance: €100The customer asks for €500 and has €100. The method quietly refuses, the ATM does not notice and says "Take your money." The error gets lost. The method could return true or false, but the caller can easily forget to check, and false does not say why.
The idea
throw: I refuse and I say why
Instead of keeping quiet, the method throws an exception:
public void withdraw(int amount) { if (amount <= 0) { throw new IllegalArgumentException("The amount must be positive"); } if (amount > balance) { throw new IllegalArgumentException("Not enough money"); } balance -= amount;}throw ends the method like return. The line balance -= amount is never reached. But instead of a value, the method sends an exception, and the caller cannot miss it:
Account account = new Account(100);try { account.withdraw(500); System.out.println("Take your money.");} catch (IllegalArgumentException e) { System.out.println("Declined: " + e.getMessage());}System.out.println("Balance: €" + account.getBalance());Declined: Not enough moneyBalance: €100newcreates the exception object, like any object. The message goes into its constructor.IllegalArgumentExceptionis the ready-made exception for "the argument is wrong". You use it in exactly this kind of check.
More precisely, if you are curious
- Another common ready-made exception is
IllegalStateException: the object is not in a state for this action, such as withdrawing from a closed account. throwworks with any object of a subclass ofThrowable. You cannot throw a string or a number.
See it
The exception falls through
public void withdraw(int amount) { if (amount > balance) { throw new IllegalArgumentException("Not enough money"); } balance -= amount;}static void payRent(Account account) { account.withdraw(800); System.out.println("The rent is paid.");}
try { payRent(new Account(500));} catch (IllegalArgumentException e) { System.out.println("Declined: " + e.getMessage());}Step by step, in words
- main calls payRent for an account with €500.
- payRent calls withdraw(800): a new frame on top.
- 800 > 500: the method throws an exception.
- The balance does not change: the frame is thrown away without finishing its lines.
- payRent has no catch: "The rent is paid." is not printed, and the frame is thrown away.
- The catch in main catches the exception, and the program continues from here.
Remember the call stack from the recursion lesson. main calls payRent, and it calls withdraw. The frames sit one on top of another.
When withdraw throws, the exception looks for a catch by going down the stack. A frame without a matching catch is thrown away right away, without finishing its lines. That is what happens to payRent too. Only main has a catch, and the program continues from there.
If main had no catch either, it would be thrown away too. Then the program stops and prints a stack trace: exactly these frames, from the inside out.
Try it
What reaches the screen?
The account has €50, and the order is €120.
What will the program print?
static void checkout(Account account) { System.out.println("Paying for the order"); account.withdraw(120); System.out.println("The order is paid");}
Account account = new Account(50);try { checkout(account); System.out.println("Sending confirmation");} catch (IllegalArgumentException e) { System.out.println("Declined: " + e.getMessage());}System.out.println("Balance: €" + account.getBalance());"Paying for the order" comes before the withdrawal. Then withdraw throws, and the frame of checkout is thrown away: "The order is paid" never shows up. In main, "Sending confirmation" is skipped too, because it is in try after the call. catch prints the refusal, and the balance is untouched.
Try it
Find the bug
The rent is €800, and the account has €500. The program says "The rent is paid." Which line is the bug on?
Pick the line with the bug.
catch catches the exception and does nothing. The error disappears, just like with the ATM in the first card. You catch where you can do something sensible. Here payRent cannot find the money, so it should not catch. Remove the try/catch: the exception will reach main, and that will tell the person.More precisely, if you are curious
- Sometimes a method catches, does something, such as writing to a log, and throws the same exception on:
throw e;incatch. - You can also throw a different exception that says better what happened. Then pass the old one as the second argument of the constructor:
new IllegalStateException("The rent is not paid", e). You will see both in the stack trace.
The idea
Your own exception
"Not enough money" is text. If the ATM wants to suggest "Deposit €30 more", it has to know how much is missing. Pulling the number out of the text is fragile. Better make your own exception class with a field:
class InsufficientFundsException extends RuntimeException { private int shortfall;
public InsufficientFundsException(int shortfall) { super("Short by €" + shortfall); this.shortfall = shortfall; }
public int getShortfall() { return shortfall; }}Your own exception is an ordinary class that inherits from RuntimeException. The constructor passes the message to the parent with super(...). The method throws it when there is not enough money:
public void withdraw(int amount) { if (amount <= 0) { throw new IllegalArgumentException("The amount must be positive"); } if (amount > balance) { throw new InsufficientFundsException(amount - balance); } balance -= amount;}And the caller catches the two cases separately:
Account account = new Account(100);try { account.withdraw(130);} catch (InsufficientFundsException e) { System.out.println(e.getMessage()); System.out.println("Deposit €" + e.getShortfall() + " more or choose a smaller amount.");} catch (IllegalArgumentException e) { System.out.println("Declined: " + e.getMessage());}Short by €30Deposit €30 more or choose a smaller amount.More precisely, if you are curious
- Exception names always end in
Exception. - Your own exception can also inherit from a more specific ready-made class, such as
IllegalArgumentException. Then acatchfor the parent catches it too. Here it is separate, so the order of the twocatchblocks does not matter.
The idea
Exception or RuntimeException?
If your own exception inherits from Exception and not from RuntimeException, it is checked. The method has to declare it with throws, and every caller has to catch it or declare it too:
class InsufficientFundsException extends Exception { ... }
public void withdraw(int amount) throws InsufficientFundsException { ...}RuntimeException, when the error is in the arguments or in the rules of the program. The caller can prevent it with a check. Most custom exceptions today are like that.Exception, when the caller has to be ready for it every time and the compiler should remind them.
A ready-made exception, when the meaning matches: IllegalArgumentException for a wrong argument, IllegalStateException for an action at the wrong moment. Your own, when the caller has to tell it apart from the others and react in its own way, or when the exception has to carry data, like shortfall.
Try it
Fill in the blank
An exception for an invalid age. What goes in the blank so that the message reaches the parent?
class InvalidAgeException extends RuntimeException {
public InvalidAgeException(String message) {
(message);
}
}super(message) calls the parent's constructor. The parent keeps the message, and getMessage() gives it back.
Exercise
Exceptions in classes
Solve these in your editor.
1. Music shop. The shop plays instruments in a demo, but not every one can be played.
- An abstract class
MusicalInstrumentwith a name, a brand and a price (private fields with getters), and a constructor. - An interface
Playablewith a methodplay(). GuitarandPianoinherit from the instrument and can play.Drumonly inherits.NotPlayableException extends RuntimeException.MusicShopwith a methoddemoInstrument(MusicalInstrument instrument): if the instrument can play (instanceof Playable playable), it plays it, otherwise it throwsNotPlayableException.
- In main, demo a Fender Stratocaster guitar (€1299), Pearl Export drums (€849) and a Yamaha P-45 piano (€499) from an array. Catch the exception for each instrument separately, so the demo goes on.
Fender Stratocaster, €1299.00The guitar plays a chord.Pearl Export, €849.00Pearl Export cannot be played in a demo.Yamaha P-45, €499.00The piano plays a scale.2. An account with rules. Add to Account a method deposit(int amount) that throws IllegalArgumentException for an amount of 0 or less. Withdrawing throws InsufficientFundsException from the lesson. The account starts with €100. Read the number of operations and then the operations themselves, each on its own line: "deposit 50" or "withdraw 500". After each one, print the balance or why the operation was declined.
4deposit 50withdraw 500withdraw -5withdraw 120Balance: €150Short by €350. Balance: €150The amount must be positive. Balance: €150Balance: €303. Invalid age. Person has a name and an age. setAge throws InvalidAgeException from the card before this one if the age is below 0 or above 150. The constructor also goes through setAge, so that nobody can create a person with an invalid age. Create Maria (19), Peter (−3) and Ivan (200).
Maria, 19 years oldPeter: Invalid age: -3Ivan: Invalid age: 200More tasks
- Table reservation. A class
Restaurantwith a number of free tables and a method for reserving one. If the tables have run out, throw your ownNoTablesException. If the number of guests is 0 or less, throw the ready-made exception for a wrong argument. - A product with stock. A class
Productwith a quantity in stock and a methodsell(int qty). If there is not enough in stock, throwOutOfStockException, which carries how many items there are. The shop catches the exception and offers to sell the ones in stock. - Music shop with a reason. Add to
NotPlayableExceptiona field with the instrument that cannot play, and print its price incatch.
Test