Return to lecture notes index

15-100 Lecture 22 (Wednesday, October 26, 2005)

Quick Quiz

Please create a new type of Exception, QuizException:

class QuizException extends Exception {
 
  public QuizException (String msg) {
    super (msg);
  }
}

Catching Exceptions

Last meeting, we discussed Java's Exception class. Today we're going to learn how to make use of this class. In particular, we'll talk about Java's exception-handling mechanism.

Remember back to the lectures where we talked about number conversion, parsing Strings to ints or doubles or floats, using methods like Integer.parseInt(). What happened if we tried to parse "asdf" to an int? The program crashed, without really telling the user what had gone wrong. How do we handle this more elegantly?

The answer is to use Java exceptions for situations such as this. Java allows the programmer to specify what the program should do if it encounters an error like this, by "catching" the exception that was "thrown" by the Integer.parseInt() method.

catch and throw are actual Java keywords for handling exceptions. Remember when we learned about Java I/O, and learned that if we were using BufferedReader and InputStreamReader, we had to include "throws IOException" after our method declaration for the main method. We did this because Java requires that if a method might "throw" an Exception that must be reported (most Java exceptions fall into this category), we must declare in the method declaration the possibility that this exception could be thrown. Any time we use I/O, there is a possibility that the input/output could fail, and this could throw an IOException.

The code below looks much like code we've seen before to read in input from the keyboard and parse it to an int.


import java.io.*;
class ExceptionExample {
	public static void main(String[] args) throws IOException, NumberFormatException {

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		System.out.print("Hey user, gimme a number: ");
		String numberString = br.readLine();

		int number = Integer.parseInt(numberString);

		System.out.println("Your number was: " + number);
	}
}

The only difference between the above and what you have normally seen is that we've added NumberFormatException to the list of exceptions that might be thrown by the main method. NumberFormatException is one of the relatively few exceptions in Java that do not have to be explicitly reported in the method declaration, but you have the option of doing so. In this case, we are reporting it because if the user enters "asdf" at the prompt, Java will try to parse it to an int, and the Integer.parseInt() method will throw a NumberFormatException, crashing our program and causing it to print out the message below:


Exception in thread "main" java.lang.NumberFormatException: For input string: "47d"
	at java.lang.NumberFormatException.forInputString(NumbreFormatException.java:48)...	

So how do we go about reporting the error to the user in a more descriptive way, without actually crashing our program? We use the Java keyword try, which tells Java that the code inside the try block (inside the scope of the squiggly braces opened after the keyword 'try') might throw an exception. This is demonstrated in the code below:


import java.io.*;
class ExceptionExample {

	public static void main(String[] args) throws IOException, NumberFormatException {
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

			System.out.print("Hey user, gimme a number: ");
			String numberString = br.readLine();
	
			int number = Integer.parseInt(numberString);
		}
		
		// Note - this will not compile, because int number is only valuable
		// inside the scope of this try block.  Variables only exist
		// inside the set of squiggly braces in which they are declared!
		// See the fix for this in the next block of code, below.
		System.out.println("Your number was: " + number);
	}
}

However, the try block in itself is not enough to handle the two exceptions that might be thrown by the main method (NumberFormatException and IOException). If exceptions are being thrown, we must "catch" them. The "try" block alerts Java that it must be ready to catch an exception; we now need a "catch" block to tell it what to do when/if it does catch an exception.

When we close the try block by adding the close-squiggly brace, we then open a "catch" block as shown below. Inside the parentheses, we specify what type of exception this catch block is designed to catch, and of course, just like any other argument, we need to give this Exception variable a name (e.g., nfe, ioe). Inside the squiggly braces for the catch block, we tell Java what to do if it catches this type of exception in the try block above. It's a good idea to use these catch blocks to tell the user what they did wrong (why the exception was thrown). Also, note that we can have a separate catch block for each type of exception that might be thrown inside the try block.


import java.io.*;
class ExceptionExample {
	
	public static void main(String[] args) throws IOException, NumberFormatException {
		int number;
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

			System.out.print("Hey user, gimme a number: ");
			String numberString = br.readLine();
	
			int number = Integer.parseInt(numberString);
		}
		catch(NumberFormatException nfe) {
			System.out.println("You idiot, please enter an --integer number--.  So there!");
			return;
		}
		catch(IOException ioe) {
			System.out.println("Error reading from input.  We broke.");
			return;
		}
		
		System.out.println("Your number was: " + number);
	}
}

In the code above, we use the return keyword by itself, without specifying anything to return. This tells Java to leave the method at that point, and since we're inside the main method, it means to simply end the program. We need to have this keyword inside the catch blocks because without it, after the catch block executed, execution would drop to the System.out.println("Your number was...") line, and if the input was invalid, we don't want to try and print it! So inside the catch blocks, the last thing we do is tell the program to exit once it's printed to the user what went wrong to cause the exception to be thrown.

What if we want to print out what type of exception was thrown? You can simply call the System.out.println() method on the Exception (in this case, we've called it 'nfe'), and it will print out "java.lang.NumberFormatException".


catch(NumberFormatException nfe) {
	System.out.println("You idiot, please enter an --integer number--.  So there!");
	System.out.println(nfe);
	return;
}

Throwing Exceptions

Last meeting, we discussed the Exception class and defining our own Exceptions. Let's do a quick review:

Exception is a class inside Java that looks basically like this:


class Exception extends Throwable {
	
	// The protected keyword means this can be accessed by members of the
	// class and members of all classes that inherit from this class
	protected String msg;

	public Exception() {
		msg = "";
	}

	public Exception(String msg) {
		this.msg = msg;
	}
}

In other words, it contains a String message, and a constructor that allows you to initialize that String to either an empty String, or to a String you pass in at initialization.

If we want to create a specific type of Exception, we follow the same basic model, and use inheritance and the extends keyword to extend the Exception class, as shown below:


class OutOfRangeException extends Exception {
	public OutOfRangeException(String msg) {
		super(msg);
	}
}

Because OutOfRangeException is a type of Exception, the properties of Exception need to be initialized (in this case, the String msg). We could recreate the text from class Exception to do this in our constructor for OutOfRangeException, but it's simpler to just call Exception's constructor using the super() keyword. This calls the Exception(String msg) constructor, using 'msg' as the argument.

Now that we have our own Exception, let's change our ExceptionExample class to use a static helper method that accepts as arguments a max value and a min value, reads in a String from the keyboard, parses it to an int, and returns that int if it is greater than or equal to min and less than or equal to max (in other words, within the range specified). This method will potentially throw an IOException (if the input/output messes up) or a NumberFormatException (if the user input is something other than an int), just like our main method before. However, inside our method, we have declared that we "throw" an OutOfRangeException if the user inputed an int that was outside the min-max window, so we also have to add this to the throws list in the method declaration.

Inside our main method, we must now add a "catch" block for this new exception as well, which we do exactly as we did for the NumberFormatException, only now we're catching an exception that we threw ourselves!


class ExceptionExample {
	public static int getNumberInRange(int min, int max) throws IOException, NumberFormatException, OutOfRangeException {
		int number;

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		System.out.print("Hey user, gimme a number: ");
		String numberString = br.readLine();
	
		number = Integer.parseInt(numberString);

		if((number < min) || (number > max))
			throw new OutOfRangeException("Number was " + numberString + 
					" but should have been within the range [" + min + ", " +
					max + "]!"0;
		
		return number;			
	}
	
	public static void main(String[] args) throws IOException,
														NumberFormatException {
		try {
			int number = getNumberInRange(1, 10);
		}
		catch(OutOfRangeException oore) {
			System.out.println("You goofed!");
			System.out.println(oore);
			return;
		}
		catch(NumberFormatException oore) {
			System.out.println("You goofed!  You should have entered an integer number!");
			System.out.println(oore);
			return;
		}
		// This catch block below will catch ANY exception that is of a class
		// that extends the Exception class, including IOException.
		catch(Exception e)
		{
			System.out.println("It broke!");
			System.out.println(e);
			return;
		}
}