(Updated September 2, 2024)
Table of contents
-
Overview
Handling Exceptions
The
try/catch/finally
BlockThe Exception Hierarchy
Checked and Unchecked Exceptions
Creating an Exception Class
Exercises
Overview
When we develop a Java program, there are many opportunities to fix syntax and logic problems. Some possibilities are available through error messages from the compiler, which refuses to allow us to move forward until the syntax errors are corrected. Other options are from our testing when logic errors are identified and repaired due to viewing results we know to be incorrect.
The problem of runtime errors, or exceptions is difficult to perceive at first if you have not given them much thought or dismissed out of hand the occasional InputMismatchException
or NumberFormatException
. If these have troubled you, there is a simple mechanism by which we can control the exceptions instead of the exceptions causing our program to halt with a stack trace abruptly.
Handling Exceptions
The use of types in programming will eventually lead to an issue of compatibility. It is easy to visualize an integer becoming a floating-point number – add a period and a zero at the end of the integer, and you now have a floating-point quantity. The compiler will allow an int
to be assigned to a float
or double
type. Ask the compiler to look the other way when assigning a double
to an int
variable, and your program will never get off the ground until you fix the “possible loss of precision” error that the compiler has identified. There are two choices:
- Make the
double
anint
permanently by changing variable types as needed and making them all the same type. - Use a cast to temporarily alter the type and acknowledge that we realize a potential for a calculation error and accept the consequences.
This method of control over our program logic and data works very well. However, what happens when we use the nextInt()
method of the Scanner
class and the data file or user provides a double
quantity? We would expect a InputMismatchException
to occur. The same would be true if the user entered “xyz” when our program was calling nextDouble()
.
We need a way to control the program and catch these exceptions before they terminate the program. This is necessary for several reasons:
- The program may recover from the error and retry the action.
- The program can detect the error and perform a controlled shutdown rather than an abrupt halt.
- The program may be easier to manage and will be written more professionally.
The try/catch/finally
Block
When an exception occurs, Java creates an object of the specific exception class. Some of these classes are the class NumberFormatException
and the class InputMismatchException
. All exception classes are subclasses of the superclass Exception
.
We can surround a set of statements that we know could generate an exception with the try
keyword, then catch
exceptions and perform work based on the type of exception that has occurred. We can also use the finally
block to cause work to be done whether an exception occurred.
Consider the following:
Scanner kb = new Scanner(System.in);
int num;
try {
System.out.print("Enter a number: ");
num = kb.nextInt();
}
catch (InputMismatchException imeRef) {
System.out.println("Your input was not an integer: " + imeRef.toString());
}
This code block enables us to try
reading an integer from the user and then catch
an exception should it occur. Note that the InputMismatchException
object is referenced with the imeRef
parameter. Let us inspect another piece of code:
Scanner kb = new Scanner(System.in);
int line=0, val;
String s;
while (kb.hasNext()) {
s = kb.next();
line++;
try {
val = Integer.parseInt(s, 16);
}
catch (Exception eRef) {
System.out.println("Possible data corruption at line " + line);
System.exit(1);
}
finally {
System.out.println("The data was \"" + s + "\"");
}
}
This example uses the superclass Exception
object with the eRef
reference parameter. The finally
block is used here as a debug tool. Recall that the finally
block will be executed regardless of the presence of an exception.
It is important to realize that the try
block ceases execution of statements as soon as an exception occurs and the the catch
blocks are only executed when an exception occurs. Never place the Exception
superclass catch above any other Exception
subclasses; otherwise, the superclass Exception
will always be applied. In other words, the order of your catch
blocks is essential.
The Exception Hierarchy
The Exception
class is a subclass of the Throwable
class which is a subclass of the Object
class. Table 1 shows some of the methods of the Throwable
class and Table 2 shows the constructors for the Exception
class. Since the class Exception
is a subclass of Throwable
, it inherits the Throwable
methods.
Throwable Class Constructors and Methods |
public Throwable() Default constructor. Creates a Throwable object with no message string. |
public Throwable(String msg) Creates a Throwable object with the message string msg. |
public String getMessage() Returns the message stored in the Throwable object. |
public void printStackTrace() Print the stack trace to view the sequence of method calls. |
public void printStackTrace(PrintWriter stream) Print the stack trace for viewing the sequence of method calls to the stream named stream. |
public String toString() Return string representation of the Throwable object. |
Table 1: Constructors and Methods of the the Throwable
class.
Exception Class Constructors |
public Exception() Default constructor. Creates an Exception object. |
public Exception(String msg) Creates an Exception object with the message string msg. |
Table 2: Constructors of the Exception class.
The exception hierarchy is relatively simple to follow. Everything is ultimately a subclass of Exception
. There are many subclasses from ClassNotFoundException
to the RunTimeException
and IOException
.
The RuntimeException
is a rather large subset of exceptions that can only occur while your program runs. As a result, you will see many subclasses listed under the RuntimeException
.
The Exception
class is defined in the java.lang
package. Other subclasses of Exception
are also defined in java.lang
as well as java.util
, java.io
and java.awt
to name a few.
Some exception classes defined in java.lang
:
Exception
ClassNotFoundException
CloneNotSupportedException
IllegalAccessException
InstantiationException
InterruptedException
NoSuchFieldException
NoSuchMethodException
RuntimeException
ArithmeticException
ClassCastException
IllegalArgumentException
NumberFormatException
IndexOutOfBoundsException
ArrayIndexOutOfBoundsException
StringIndexOutOfBoundsException
NullPointerException
Some exception classes defined in java.util
:
Exception
RuntimeException
EmptyStackException
NoSuchElementException
InputMismatchException
Some exception classes defined in java.io
:
Exception
IOException
EOFException
FileNotFoundException
The online Java documentation at the Oracle site is the best place to determine what exceptions a method or constructor will throw.
As a final note about handling multiple exceptions, you could always catch the superclass Exception
and then process an if/else-if/else-if/else
against the superclass reference parameter like so:
try {
// sequence of input and
// arithmetic statements that could
// produce Scanner or division by zero
// errors.
}
catch (Exception eRef) {
if ( eRef instanceof ArithmeticException)
// statements
else if (eRef instanceof InputMismatchException)
// statements
else
// statements
}
Checked and Unchecked Exceptions
Java has broken down the predefined exceptions into two categories: checked and unchecked. Checked exceptions are made by the compiler, meaning an exception that the compiler can successfully identify.
The FileNotFoundException
is a prime example, as is the IOException
. Recall that this exception must be thrown often by the main()
method of your program. This results in a throws FileNotFoundException
at the end of the main()
declaration for any program that uses FileReader
or PrintWriter
. Of course, we ignore the exception since we have not set up a try/catch
block to properly deal with the potential problem.
Unchecked exceptions are those that you may choose to catch or not. These are also exceptions that the compiler cannot accurately detect. These types of exceptions include InputMismatchException
, NumberFormatException
and ArithmeticException
. Essentially, all of the RuntimeException
subclasses are unchecked and should be checked by you, the programmer, if you wish to make your programs more reliable and achieve reasonable recoverability.
When writing your methods, remember that you may ignore, throw, or re-throw a caught exception. To throw or re-throw an exception, you need to throw an instantiated exception object. For example:
throw new Exception("Text associated with the exception");
Or with:
throw new ArithmeticException("bad math skills!");
Or in the case of rethrowing:
try {
// statements
}
catch (InputMismatchException imeRef) {
// statements
throw imeRef;
}
Creating an Exception Class
Now, we will build a program that creates an exception class for improperly formatted data. This program requires input containing at least two fields in a string separated by a colon.
We will start with the class that defines a DataFormatExeption
. This exception extends the Exception
class and defines two constructors similar to Exception
. The constructors call the superclass constructor super()
. This class is shown in Example 1.
public class DataFormatException extends Exception {
public DataFormatException () {
super("Data must contain at least one colon");
}
public DataFormatException(String msg) {
super(msg);
}
}
Example 1: Code defining the DataFormatException
class.
The class that will exercise our new DataFormatException
will also demonstrate how to catch and recover from the exception. In this case, we are showing that you do not need to stop execution just because something terrible has occurred. (Although sometimes that is all that is left to you!)
import java.util.Scanner;
public class ExceptTest {
static Scanner kb = new Scanner(System.in);
public static void main(String[] args) {
String s;
int pos;
System.out.println("\nEnter data with at least one colon:");
while (kb.hasNext()) {
try {
s = kb.nextLine();
pos = s.indexOf(":");
if ( pos == -1 )
throw new DataFormatException();
else
System.out.println("The value entered is " + s);
}
catch (DataFormatException dfeRef) {
System.out.println(dfeRef);
}
catch (Exception eRef) {
System.out.println(eRef);
}
System.out.println("\nEnter data with at least one colon:");
}
}
}
Example 2: Program to exercise the DataFormatException
class.
The program ExceptTest
in Example 2 uses an EOF-based while
loop. You could, of course, use a do-while
. Consider how your program needs to be constructed to facilitate moving forward in the event of an exception.
This program also throws the exception within the try
block, which is subsequently caught in the catch
block. This demonstrates the tight level of control that the try/catch
block offers a programmer.
Notice how the while
loop contains the try/catch
block and how there is no call to System.exit()
as a result of the exception occurring. Remember that our goal is to keep the program going. Any data entry without a colon will result in the exception being displayed.