CSC1120: Exceptions
Contents
Overview
As defined by Oracle, An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions. Exception handling is a technique for handling such exceptions gracefully.
The first exceptions we’ll look at deal with invalid user input. Have you ever crashed a program (made it terminate ungracefully) due to invalid input? If a program calls the Scanner class’s nextInt method and a user enters a noninteger, the JVM generates an exception, displays a big, ugly error message, and terminates the program. Here’s a sample session that illustrates what we’re talking about:
Note the InputMismatchException above. That’s the type of exception that’s generated when a user enters a non-Integer in response to a nextInt() method call. Note the exception message. Exception messages can be annoying, but they serve a useful purpose. They provide information about what’s gone wrong. You can see all of the classes, methods, and line numbers from which the code passed through before the exception occurred.
Try/Catch Blocks
Some code has a chance of causing an exception, such as the nextInt() method call above. When code could cause an exception to occur, it is unofficially called dangerous code. We often need to implement dangerous code, especially when getting user input, as users can often introduce unexpected or incorrect input values. There are several ways to deal with dangerous code. The first is a construct called a try/catch block. It has a similar syntax to an if/else block, where it will first “try” to run the code inside the try block. If no exceptions occur, it does not run the code in the catch block and continues with the program. If an exception is generated, the code in the try block immediately stops executing, the program skips to the catch block, and the code inside the catch block is executed.
try {
statement(s)
} catch (exception-class parameter) {
error-handling code
}
Throwing an Exception
When the JVM (Java Virtual Machine) creates an exception object, we say that it "throws an exception." Technically, it throws an "exception object," but most programmers don’t worry about the difference between an exception (which is an event) and an exception object. It’s not a big deal.
When the JVM throws an exception, it looks for a matching catch block to handle it. If it finds a matching catch block, it runs the code in that block. If it doesn’t find one, the JVM prints out the error message and stops the program. A "matching catch block" is one where the type of error (exception) matches the type in the catch block’s parameter.
An exception object includes information about the error, like what kind of error it is and a list of the method calls that led to it. Right now, we only need the exception object to match with the right catch block, but later we can use the extra information it has.
Example:
/*
* Course: CSC1020
* LinePlot.java
* Exceptions Example
* Dean & Dean
*/
import java.util.Scanner;
/**
* A class that models a line in a Cartesian graph
*/
public class LinePlot {
private int oldX = 0; // oldX and oldY save previous point
private int oldY = 0; // starting point is the origin (0,0)
/**
* This method prints the description of a line segment from the
* previous point to the current point
*/
public void plotSegment(int x, int y) {
System.out.println("New segment = (" + oldX + "," + oldY +
")-(" + x + "," + y + ")"); oldX = x; oldY = y; }
public static void main(String[] args) {
Scanner stdIn = new Scanner(System.in);
LinePlot line = new LinePlot();
String xStr
String yStr; // coordinates for point in String form
int x, y; // coordinates for point
System.out.print("Enter x & y coordinates (q to quit): “);
xStr = stdIn.next();
while (!xStr.equalsIgnoreCase(“q")) {
yStr = stdIn.next();
try {
x = Integer.parseInt(xStr);
y = Integer.parseInt(yStr);
line.plotSegment(x, y);
} catch (NumberFormatException e) {
System.out.println("Invalid entry: " + xStr + " " +
yStr + "\nMust enter integer space integer.");
}
System.out.print("Enter x & y coordinates (q to quit): “);
xStr = stdIn.next();
}
}
}
try Block Details
Now that you know the basic idea behind try blocks, it’s time to flesh out some subtle try block details.
try Block Size
Deciding how big your try blocks should be is a bit tricky. Sometimes, it's better to use small try blocks, and other times, bigger ones are better. You can legally put an entire method inside a try block, but that's not usually a good idea because it makes it harder to spot the dangerous code. In general, it's best to keep your try blocks small enough so you can easily see where the risky code is.
However, if you have several related risky statements in a row, it might be better to wrap them all in one big try block instead of making a separate try block for each one. Too many small try blocks can make your code messy, while one big try block can make your code easier to read. For example, in the LinePlot program, both parseInt statements are in the same try block because they are connected and close to each other, which makes the code clearer to read.
Assume That try Block Statements Are Skipped
When an exception is thrown, the JVM instantly jumps out of the try block. This means that any statements after the one that caused the exception will be skipped. The compiler, which checks the code, expects the worst and assumes all the statements in the try block might get skipped. So, if you have a try block with a line that assigns a value to x
, the compiler assumes that the assignment might not happen. If there's no assignment to x
outside the try block and the value of x
is needed, you’ll get an error message saying:
variable x might not have been initialized
If you see this error, you can usually fix it by giving x
an initial value before the try block starts.
Checked and Unchecked Exceptions
Exceptions are divided into two types—checked and unchecked. Checked exceptions must be handled with a try-catch block. Unchecked exceptions can be handled with a try-catch block, but it’s not required.
How to Identify an Exception’s Type
How do you know if an exception is checked or unchecked? Since an exception is an object, it belongs to a class. To figure out if an exception is checked or unchecked, look up its class on Oracle’s Java API website. Once you find the class, check its ancestors (the classes it inherits from). If it’s a descendant of the RuntimeException
class, it’s an unchecked exception. If not, it’s a checked exception. For example, if you look up NumberFormatException
on Oracle’s Java API website, you’ll see this:
This shows that the NumberFormatException
class is a descendant of the RuntimeException class, so the NumberFormatException
class is an unchecked exception.
Unchecked Exceptions
As you learned in the previous section, unchecked exceptions need not be checked with a try - catch mechanism. However, at runtime, if the JVM throws an unchecked exception and there’s no catch block to catch it, the program will crash.
Strategies for Handling Unchecked Exceptions
If your program contains code that might throw an unchecked exception, there are two alternate strategies for dealing with it: Use a try - catch structure. Don’t attempt to catch the exception, but write the code carefully so as to avoid the possibility of the exception being thrown.
Checked Exceptions
Let’s now look at checked exceptions. If a code fragment has the potential of throwing a checked exception, the compiler forces you to associate that code fragment with a try - catch mechanism. If there is no associated try - catch mechanism, the compiler generates an error. With unchecked exceptions, you have a choice of how to handle them—a try - catch mechanism or careful code. With checked exceptions, there’s no choice—you must use a try - catch mechanism.
Using throws to Postpone the catch
When it's not possible to use try and catch blocks directly in a method with risky code, you can move those blocks to the method that called it. If an exception is thrown in the risky code, the JVM jumps out of that method and passes the exception back to the try and catch blocks in the calling method. But when should you do this? Most of the time, you should keep the try and catch blocks in the method with the risky code because it helps keep the code organized, which is a good thing. However, sometimes it's hard to write the right catch block inside the risky method. For example, if you've written a utility method that’s used in many places and it throws an exception, you might want an error message that fits each calling method. In this case, it's better to put the try and catch blocks in the calling methods.
Another example is when you have a method that’s supposed to return a value (non-void) but sometimes throws an exception. The compiler expects the method to always return something, but if an exception is thrown, there might not be a value to return. How can you avoid returning a value when there's an exception? You move the try and catch blocks to the calling method. When an exception is thrown, the JVM goes back to the calling method without returning a value, and the calling method's try and catch blocks handle the error, likely with an error message.