95.105/145 - Introduction to Programming |
Fall 2001
|
10 Exception Handling |
10.1 Exceptions |
Exceptions are:
Java has many predefined Exceptions, and we can also create our own. An Exception is an objects, so each one has its own class definition in Java. The Exception classes are arranged in a hierarchy, and their position in the hierarchy can affect the way that they are handled.
There are three main kinds of errors:
The Error class and
its subclasses represent a serious problem (i.e, unrecoverable error).
Generally, applications should not try to catch them.
There are many subclasses, here are just a few:
|
![]() |
The Exception class and its subclasses indicate a less serious problem. The Exceptions are either "checked" or "unchecked". Generally, applications should try to catch the "checked" Exceptions. The Java Virtual Machine detects these kinds of Exceptions and forces you to deal with them before it will compile your code.
This checking is made possible since
Delegating Responsibility
When we do not wish to handle an error in our code, we can delegate the responsibility to the "calling method". We do this by adding a throws clause to our method declaration:
public void openThisFile(String fileName) throws java.io.FileNotFoundException {A method may throw more than one Exception. We can declare as many as we want with a single throws clause:
// code for method
}
public Object convertFileToObject(String fileName)The throws clause is part of a method declaration used to (tell the compiler) which Exceptions the method may throw back to its caller. (We've seen this before when getting keyboard input by saying throws IOException. The throws clause is required if the code in the method may generate, but not handle, an Exception.
throws java.io.FileNotFoundException, java.lang.ClassNotFoundException {
// code for method
}
Notice now that the calling method may also delegate the responsibility to "its" calling method as well.
public void getCustomerInfo() throws java.io.FileNotFoundException {Here, if the Exception is thrown while in the openThisFile() method, the getCustomerInfo() method will stop and it will then pass on the Exception to its caller. The responsibility may be repeatedly delegated in this manner. Everyone essentially ignores the error (like a hot potato). Nobody explicitly handles the error. The JVM will eventually catch it and halt the program:
// do something
this.openThisFile(“customer.txt”);
// do something
}
At any time during this process however, any method may catch the exception and handle it. Once caught, propagation of the Exception stops.
A method may catch an exception by specifying try and catch blocks.
The try block represents a sequence of java statements (defined between brackets { }) in which the result of any method calls or other operations might cause an exception. We precede this block with the try keyword.
The catch block represents a sequence of java statements (defined between brackets { }) which follow the try block prefaced by the word catch. This block of code defines the code that is to be evaluated when a specific type of exception occurs.
Here is the typical format for doing a try with many catches:
try {
//some code that may do something causing an exception } catch (<ExceptionType1> e) { //some code that handles the exception } catch (<ExceptionType2> e) { //some code that handles the exception } catch (<ExceptionType3> e) { //some code that handles the exception } ... finally { //some code that is always executed } |
The method does not need to throw the exception any further (i.e., no throws clause):
public void getCustomerInfo() {The catch block requires a parameter which indicates the type of error to be caught. We also specify a name for the parameter and thus we can access and use the Exception object within the catch block.
try {
openThisFile(“customer.txt”);
}
catch (java.io.FileNotFoundException ex){
// Handle the error here
}
}
More than one catch block may be used to catch one-of-many possible Exceptions. We simply list all catch blocks one after another:
public void getCustomerInfo() {Here is what happens when Java encounters try/catch blocks:
try {
// do something that may cause an Exception
}
catch (java.io.FileNotFoundException ex){
// Handle the error here }
catch (java.lang.NullPointerException ex){
// Handle the error here }
catch (java.lang.ArithmeticException ex){
// Handle the error here }
}
Note that only one catch block (the one that first matches the Exception) will ever be evaluated.
Be careful !! The order of the catch blocks is important when you start to use the hierarchy. A more general Exception will be caught before one of its subclasses. So we may end up with catch blocks that are unreachable!! The compiler will tell you if this happens.
public void getCustomerInfo() {A finally block may be used after the catch blocks:
try {
// do something that may cause an Exception
}
catch (java.lang.Exception ex){
// Catches all Exceptions
}
catch (java.io.IOException ex){
// Never reached since above catches all
}
catch (java.io.FileNotFoundException ex){
// Never reached since above two are caught first
}
}
try {The finally block is used to release resources, such as closing files. It is evaluated:
...
}
catch (java.io.IOException ex){}
catch (java.lang.Exception ex){}
finally {
// Code to release resources
}
catch (<ExceptionType1> e) {Consider the stack trace for this code:
System.out.println("Hey! Something bad just hapenned!");
}
catch (<ExceptionType4> e) {
System.out.println(e.getMessage());
}
catch (<ExceptionType3> e) {
e.printStackTrace();
}
public class MyClass {When we run this code, we get the following stack trace printed to the console window:
public static void doSomething(int[] anArray){
doAnotherThing(anArray);
}
public static void doAnotherThing(int[] theArray){
System.out.println(theArray[0]);
}
public static void main(String[] args){
doSomething(null);
}
}
java.lang.NullPointerExceptionNotice that the stack trace indicates:
at MyClass.doAnotherThing(MyClass.java:7)
at MyClass.doSomething(MyClass.java:5)
at MyClass.main(MyClass.java:3)
10.2 Examples of Handling Exceptions |
System.out.println("Enter the first number:");
number1 = Integer.valueOf(inputStream.readLine()).intValue();
System.out.println("Enter the second number:");
number2 = Integer.valueOf(inputStream.readLine()).intValue();
System.out.print(number2 + " goes into " +
number1);
System.out.print(" this many times: ");
result = number1 / number2;
System.out.println(result);
}
}
Here is the output if 143 and 24 are entered:
Enter
the first number:
143 Enter the second number: 24 24 goes into 143 this many times: 5 |
What if we now enter 143 and ABC ?
Enter
the first number:
143 Enter the second number: ABC java.lang.NumberFormatException: ABC at java.lang.Integer.parseInt(Integer.java:229) at java.lang.Integer.valueOf(Integer.java:310) at Except1.main(Except1.java:12) |
Yep, this is not a nice way to handle the exception. By default, when exceptions occur, they actually print out what is known as a stack trace. This is the sequence of method calls that led to the exception. It is ugly, but good for debugging purposes.
What if we enter 143 and 0 ?
Enter
the first number:
143 Enter the second number: 0 0 goes into 143 this many times: java.lang.ArithmeticException: / by zero at Except1.main(Except1.java:14) |
Once again, this is not nice at all.
Lets try to handle the errors. First, we'll handle the I/O errors by printing out an appropriate message and then quitting the program. Note that we can quit the program at any time by using the System.exit() call.
try {
System.out.println("Enter the first number:");
number1 = Integer.valueOf(inputStream.readLine()).intValue();
System.out.println("Enter the second number:");
number2 = Integer.valueOf(inputStream.readLine()).intValue();
}
catch (NumberFormatException e) {
System.out.println("Those were not proper integers!
I quit!");
System.exit(-1);
}
System.out.print(number2 + " goes into " +
number1);
System.out.print(" this many times: ");
result = number1 / number2;
System.out.println(result);
}
}
Enter
the first number:
143 Enter the second number: ABC Those were not proper integers! I quit! |
What if we enter ABC as the first number ?
Enter
the first number:
ABC Those were not proper integers! I quit! |
Woops! It appears that our error message is not grammatically correct anymore. Perhaps we should change it to "Invalid integer entered!" and this should be clear enough.
Lets now try to handle the divide by zero exception (actually an ArithmeticException):
Enter
the first number:
143 Enter the second number: 0 Second number is 0, cannot do division! |
Can we merge the two try blocks into one ?
for (int i=0; i<2; i++) {
valid = false;
while (!valid) {
try {
System.out.println("Enter number " + (i+1));
number[i] = Integer.valueOf(inputStream.readLine()).intValue();
valid = true;
}
catch (NumberFormatException e) {
System.out.println("Invalid integer entered. Please
try again.");
}
}
}
try {
result = number[0] / number[1];
System.out.print(number[1] + " goes into "
+ number[0]);
System.out.println(" this many times: " +
result);
}
catch (ArithmeticException e) {
System.out.println("Second number is 0, cannot do
division!");
}
}
}
Enter
number 1
what Invalid integer entered. Please try again. Enter number 1 help me Invalid integer entered. Please try again. Enter number 1 ok, ok, here goes Invalid integer entered. Please try again. Enter number 1 143 Enter number 2 did you say number 2 ? Invalid integer entered. Please try again. Enter number 2 40 40 goes into 143 this many times: 3 |
10.3 Creating and Throwing Your Own Exceptions |
throw new java.io.FileNotFoundException();
throw new NullPointerException(); throw new Exception(); |
Methods that throw these Exceptions, must declare that they do so in
their method declarations, using the throws clause (as we have seen
before).
You may even catch an Exception, partially handle it and then throw
it again !
public void getCustomerInfo() throws Exception {
try { ... }
catch (Exception e){
// Handle (partially) the exception here
throw e; // throw it again
}
}
It is possible to create your own Exceptions. Simply create a subclass of an existing Exception. If you are unsure where to put it in the hierarchy, use Exception as the superclass. When making an exception, you should:
public class
MyExceptionName
extends SuperclassOfMyException {
public MyExceptionName() { super("Some string explaining the exception"); } } |
You save this Exception in its own file and compile it as any other Java file. Then, you can start using it in your program.
Example
Lets look at our previous example and our own DivideByZeroException. Where does it go in the Exception hierarchy ? Well it should go as a subclass of ArithmeticException since this is where things went wrong initially. First, we must make the particular Exception subclass:
import java.io.*;Here is the result when 143 and 0 are entered:
public class Except6 {
private static int quotient(int numerator, int denominator) throws DivideByZeroException {
if (denominator == 0)
throw new DivideByZeroException();
return(numerator / denominator);
}public static void main(String args[]) throws IOException {
BufferedReader inputStream = new BufferedReader
(new InputStreamReader(System.in));
int result = 0, number1 = 0, number2 = 0;try {
System.out.println("Enter the first number:");
number1 = Integer.valueOf(inputStream.readLine()).intValue();
System.out.println("Enter the second number:");
number2 = Integer.valueOf(inputStream.readLine()).intValue();
result = quotient(number1, number2);
System.out.print(number2 + " goes into "+ number1);
System.out.println(" this many times: " + result);
}
catch (NumberFormatException e) {
System.out.println("Invalid integer entered!");
System.exit(-1);
}
catch (DivideByZeroException e) {
System.out.println(e.toString());
System.exit(-1);
}
}
}
Enter
the first number:
143 Enter the second number: 0 DivideByZeroException: Attempted to divide by zero |
Notice that we made use of the Exception description by merely sending the toString() message.
Lets take another look at the BankAccount object again ... more specifically ...the withdraw method:
public boolean withdraw(float anAmount) {When the user tries to withdraw more money than is actually in the account...nothing happens. Since the method returns a boolean, we can always check for this error where we call the method:
if (anAmount <= balance) {
balance -= anAmount;
return true;
}
return false;
}
public static void main(String args[]) {Note that this form of error checking works fine. However, it clearly clutters up the code! Lets see how we can use Exceptions.
BankAccount b = new BankAccount("Bob");
b.deposit(100);
b.deposit(500.00f);
if (!b.withdraw(25.00f))
System.out.println("Error withdrawing money from account");
if (!b.withdraw(189.45f))
System.out.println("Error withdrawing money from account");
b.deposit(100.00f);
if (!b.withdraw(1000000))
System.out.println("Error withdrawing money from account");
}
We'll create an WithdrawalException. Where would it go in the Exception hierarchy ? Probably right under the Exception class.
Here is the Exception:
public boolean withdraw(float anAmount) throws WithdrawalException {Note that we must also instruct the compiler that this method may throw an WithdrawalException by writing this as part of the method declaration. The addition of this simple statement means that whichever methods call the withdraw method, MUST handle the exception.
if (anAmount <= balance) {
balance -= anAmount;
return true;
}
throw new WithdrawalException();
return false;
}
Also notice that we no longer need the return type for the withdraw method since its purpose was solely for error checking. Now that we have the Exception being generated, this becomes our form of error checking. Let us alter the return type:
public void withdraw(float anAmount) throws WithdrawalException {There. That's better. Now how do we change our "calling" code ?
if (anAmount <= balance)
balance -= anAmount;
throw new WithdrawalException();
}
public static void main(String args[]) {Notice how much simpler and cleaner the calling code becomes. We can make our code even more simpler by simply ignoring the error...but then the program will stop (in our example):
BankAccount b = new BankAccount("Bob");
try {
b.deposit(100);
b.deposit(500.00f);
b.withdraw(25.00f);
b.withdraw(189.45f);
b.deposit(100.00f);
b.withdraw(1000000);
} catch (WithdrawalException e) {
System.out.println("Error withdrawing money from account");
}
}
public static void main(String args[]) throws WithdrawalException {We could have also created a constructor in our Exception class that takes a String parameter to describe the error:
BankAccount b = new BankAccount("Bob");
b.deposit(100);
b.deposit(500.00f);
b.withdraw(25.00f);
b.withdraw(189.45f);
b.deposit(100.00f);
b.withdraw(1000000);
}
public class WithdrawalException extends Exception {We can then use this new constructor instead by supplying different explanations as to why the error occurred. For example, there may be many reasons why we cannot withdraw from a BankAccount:
public WithdrawalException(String desc) {
super(desc);
}
}
publicstatic void main(String args[]) {Note that the catch block catches any errors for both accounts. Note also that the reason for the error may be different. For example, the SuperSavingsAccount may have the following withdraw() method:
PowerSavingsAccount p = new PowerSavingsAccount("Bob");
SuperSavingsAccount s = new SuperSavingsAccount("Betty");
try {
p.deposit(100);
s.deposit(500.00f);
p.withdraw(25.00f);
p.withdraw(189.45f);
s.deposit(100.00f);
s.withdraw(1000000);
} catch (WithdrawalException e) {
System.out.println(e.getMessage());
}
}
public void withdraw(float anAmount) throws WithdrawalException {whereas the PowerSavingsAccount may have this method:
throw new WithdrawalException(“Withdrawals are not allowed from this account”);
}
public void withdraw(float anAmount) throws WithdrawalException {It is neat how we can kinda "encapsulate" different explanations for the error within a single Exception type !!!
if (anAmount > balance)
throw new WithdrawalException(“Insufficient funds in account to withdraw specified amount”);
if (anAmount + WITHDRAW_FEE > balance) {
throw new WithdrawalException(“Not enough money to cover transaction fee”);
balance -= anAmount + WITHDRAW_FEE;
}