Session 17: More on exceptions

Checked and unchecked exceptions (Section 8.1)
String parsing exceptions
Multiple catch blocks
Variables in try clauses
Exercise

Checked and unchecked exceptions

Textbook: Section 8.1

Sometimes, the Java compiler will insist that you catch a particular type of exception. These are called unchecked exceptions because Java will not insist that they be checked. The exceptions we've mentioned so far have all been unchecked.

One example of a method that throws a checked exception is Thread.sleep(), which will halt for a number of milliseconds specified as a parameter.

Thread.sleep(20); // won't compile: exception not caught
This method can throw an InterruptedException, indicating that the method wasn't able to sleep as long as requested because of some other signal it received. Java will refuse to compile any code that calls Thread.sleep() but doesn't specifically handle this exception.

So what you must do is place the code that uses Thread.sleep() within a try clause, followed by a catch clause to handle an InterruptedException.

try {
    Thread.sleep(20);
} catch(InterruptedException e) { }
Notice that here, we've told Java to do nothing when the exception occurs - essentially, we just want to ignore the exception. For this particular exception, this is frequently the best thing you can do.

(In fact, we used Clock.sleep() in Labs 2 and 4, defined in the csbsju.cs160 package. We did this instead of using the Thread class in the java.lang package because doing that instead would force us to worry about this checked exception, and we hadn't studied exceptions yet.)

String parsing exceptions

The Integer.parseInt() and Double.parseDouble() class methods both take a String parameter and converts it into a number. But what if the string doesn't look like a number? Then it throws an exception (a NumberFormatException, in this case).

try {
    IO.print("Type a number: ");
    String str = IO.readLine();
    int i = Integer.parseInt(str);
    IO.println(2 * i);
} catch(NumberFormatException e) {
    IO.println("That's not a number.");
}
Integer.parseInt() will raise a NumberFormatException if the line contains any characters that don't belong in a number. This provides a more robust technique for checking the user's input. So the user might experience the following.
Type a number: 3 2
That's not a number.
On the other hand, if there wasn't a problem with what the user types, it would simply proceed normally.
Type a number: 45
90

Multiple catch blocks

Occassionally, you may run into a situation where you want multiple catch blocks for the same try block. You can get around this by stringing the catch blocks together, as the following illustrates.

try {
    IO.println("Type two numbers to divide.");
    int m = Integer.parseInt(IO.readLine());
    int n = Integer.parseInt(IO.readLine());
    IO.println(m / n);
} catch(NumberFormatException e) {
    IO.println("That's not a number.");
} catch(ArithmeticException e) {
    IO.println("I can't divide by zero.");
}

Variables in try clauses

Quite frequently you'll want to declare a variable in a try block that you want to use after the try block. Like all other statement blocks, variables declared inside the block can't be used outside the block. You might want to write the former as follows.

IO.println("Type two numbers to divide.");
try {
    int m = Integer.parseInt(IO.readLine());
    int n = Integer.parseInt(IO.readLine());
} catch(NumberFormatException e) {
    IO.println("That's not a number.");
}
try {
    IO.println(m / n); // ERROR: won't compile
} catch(ArithmeticException e) {
    IO.println("I can't divide by zero.");
}
This won't compile, since in the expression m / n, neither m nor n are defined as variables. (We did define them in the first try block, but in the second try block we were beyond the closing brace enclosing their definition, so that definition no longer applies.)

So to make this strategy work, you'd need to declare the variables outside the block. We'll also want to give these variables an initial value, in case an exception raised inside the first block could prevent one from getting a value otherwise.

IO.println("Type two numbers to divide.");
int m = 0;
int n = 1;
try {
    m = Integer.parseInt(IO.readLine());
    n = Integer.parseInt(IO.readLine());
} catch(NumberFormatException e) {
    IO.println("That's not a number.");
}
try {
    IO.println(m / n); // ERROR: won't compile
} catch(ArithmeticException e) {
    IO.println("I can't divide by zero.");
}
Frankly, I prefer my earlier solution with multiple catch blocks more.

Generally, exceptions are an important but irritating part of Java programming. You can't really ignore them, but unfortunately neither can you assume the outside world will always give you proper values with which to do your work.

Exercise