An exception is an anomalous condition that alters or interrupts the flow of execution. Java provides built-in exception handling to deal with such conditions. Exception handling should not be part of the normal program flow.
As shown in Figure 7-1, all exceptions and errors inherit from the class Throwable
, which inherits from the class Object
.
Exceptions and errors fall into three categories: checked exceptions, unchecked exceptions, and errors.
Checked exceptions are checked by the compiler at compile time.
Methods that throw a checked exception must indicate so in the method declaration using the throws
clause. This must continue all the way up the calling stack until the exception is handled.
All checked exceptions must be explicitly caught with a catch
block.
Checked exceptions include exceptions of the type Exception
, and all classes that are subtypes of Exception
, except for RuntimeException
and the subtypes of RuntimeException
.
The following is an example of a method that throws a checked exception:
// Method declaration that throws
// an IOException
void
readFile
(
String
filename
)
throws
IOException
{
...
}
The compiler does not check unchecked exceptions at compile time.
Unchecked exceptions occur during runtime due to programmer error (e.g., out-of-bounds index, divide by zero, and null pointer exception) or system resource exhaustion.
Unchecked exceptions do not have to be caught.
Methods that may throw an unchecked exception do not have to (but can) indicate this in the method declaration.
Unchecked exceptions include exceptions of the type RuntimeException
and all subtypes of RuntimeException
.
There are various checked exceptions, unchecked exceptions, and unchecked errors that are part of the standard Java platform. Some are more likely to occur than others.
ClassNotFoundException
Thrown when a class cannot be loaded because its definition cannot be found.
IOException
Thrown when a failed or interrupted operation occurs. Two common subtypes of IOException
are EOFException
and FileNotFoundException
.
FileNotFoundException
Thrown when an attempt is made to open a file that cannot be found.
SQLException
Thrown when there is a database error.
InterruptedException
Thrown when a thread is interrupted.
NoSuchMethodException
Thrown when a called method cannot be found.
CloneNotSupportedException
Thrown when clone()
is called by an object that is not cloneable.
ArithmeticException
Thrown to indicate that an exceptional arithmetic condition has occurred.
ArrayIndexOutOfBoundsException
Thrown to indicate an index is out of range.
ClassCastException
Thrown to indicate an attempt to cast an object to a subclass of which it is not an instance.
DateTimeException
Thrown to indicate problems with creating, querying, and manipulating date-time objects.
IllegalArgumentException
Thrown to indicate that an invalid argument has been passed to a method.
IllegalStateException
Thrown to indicate that a method has been called at an inappropriate time.
IndexOutOfBoundsException
Thrown to indicate that an index is out of range.
NullPointerException
Thrown when code references a null object but a nonnull object is required.
NumberFormatException
Thrown to indicate an invalid attempt to convert a string to a numeric type.
UncheckedIOException
Wraps an IOException with an unchecked exception.
AssertionError
ExceptionInInitializeError
Thrown to indicate an unexpected exception in a static initializer.
VirtualMachineError
Thrown to indicate a problem with the JVM.
OutOfMemoryError
Thrown when there is no more memory available to allocate an object or perform garbage collection.
NoClassDefFoundError
Thrown when the JVM cannot find a class definition that was found at compile time.
StackOverflowError
In Java, error-handling code is cleanly separated from error-generating code. Code that generates the exception is said to “throw” an exception, whereas code that handles the exception is said to “catch” the exception:
// Declare an exception
public
void
methodA
()
throws
IOException
{
...
throw
new
IOException
();
...
}
// Catch an exception
public
void
methodB
()
{
...
/* Call to methodA must be in a try/catch block
** since the exception is a checked exception;
** otherwise methodB could throw the exception */
try
{
methodA
();
}
catch
(
IOException
ioe
)
{
System
.
err
.
println
(
ioe
.
getMessage
());
ioe
.
printStackTrace
();
}
}
Thrown exceptions are handled by a Java try, catch, finally
block. The Java interpreter looks for code to handle the exception, first looking in the enclosed block of code, and then propagating up the call stack to main()
if necessary. If the exception is not handled on the main thread (i.e., not the Event Dispatch Thread [EDT]) or a thread that you created, the program exits and a stack trace is printed:
try
{
method
();
}
catch
(
EOFException
eofe
)
{
eofe
.
printStackTrace
();
}
catch
(
IOException
ioe
)
{
ioe
.
printStackTrace
();
}
finally
{
// cleanup
}
The try-catch
statement includes one try
and one or more catch
blocks.
The try
block contains code that may throw exceptions. All checked exceptions that may be thrown must have a catch
block to handle the exception. If no exceptions are thrown, the try
block terminates normally. A try
block may have zero or more catch
clauses to handle the exceptions.
A try
block must have at least one catch
or finally
block associated with it, or it can omit the catch as long as the method throws the exception.
There cannot be any code between the try
block and any of the catch
blocks (if present) or the finally
block (if present).
The catch
block(s) contain code to handle thrown exceptions, including printing information about the exception to a file, which gives users an opportunity to input correct information. Note that catch
blocks should never be empty because such “silencing” results in exceptions being hidden, which makes errors harder to debug.
A common convention for naming the parameter in the catch
clause is a set of letters representing each of the words in the name of the exception:
catch
(
ArrayIndexOutOfBoundsException
aioobe
)
{
aioobe
.
printStackStrace
();
}
Within a catch
clause, a new exception may also be thrown if necessary.
The order of the catch
clauses in a try/catch
block defines the precedence for catching exceptions. Always begin with the most specific exception that may be thrown and end with the most general.
Exceptions thrown in the try
block are directed to the first catch
clause containing arguments of the same type as the exception object or superclass of that type. The catch
block with the Exception
parameter should always be last in the ordered list.
If none of the parameters for the catch
clauses match the exception thrown, the system will search for the parameter that matches the superclass of the exception.
The try-finally
statement includes one try
and one finally
block. The finally
block is used for releasing resources when necessary:
public
void
testMethod
()
throws
IOException
{
FileWriter
fileWriter
=
new
FileWriter
(
"\data.txt"
);
try
{
fileWriter
.
write
(
"Information..."
);
}
finally
{
fileWriter
.
close
();
}
}
This block is optional and is only used where needed. When used, it is executed last in a try-finally
block and will always be executed, whether or not the try
block terminates normally. If the finally
block throws an exception, it must be handled.
The try-catch-finally
statement includes one try
, one or more catch
blocks, and one finally
block.
For this statement, the finally
block is also used for cleanup and releasing resources:
public
void
testMethod
()
{
FileWriter
fileWriter
=
null
;
try
{
fileWriter
=
new
FileWriter
(
"\data.txt"
);
fileWriter
.
write
(
"Information..."
);
}
catch
(
IOException
ex
)
{
ex
.
printStackTrace
();
}
finally
{
try
{
fileWriter
.
close
();
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
}
This block is optional and is only used where needed. When used, it is executed last in a try-catch-finally
block and will always be executed, whether or not the try
block terminates normally or the catch
clause(s) were executed. If the finally
block throws an exception, it must be handled.
The try-with-resources
statement is used for declaring resources that must be closed when they are no longer needed. These resources are declared in the try
block. Java 9 simplifies the statement:
// Java 7 and 8
public
void
testMethod
()
throws
IOException
{
FileWriter
fileWriter
=
new
FileWriter
(
"\data.txt"
);
try
(
FileWriter
fw
=
fileWriter
)
{
fw
.
write
(
"Information..."
);
}
}
// Java 9
public
void
testMethod
()
throws
IOException
{
FileWriter
fileWriter
=
new
FileWriter
(
"\data.txt"
);
try
(
fileWriter
)
{
f1
.
write
(
"Information..."
);
}
}
Any resource that implements the AutoClosable
interface may be used with the try-with-resources
statement.
The multi-catch
clause is used to allow for multiple exception arguments in one catch
clause:
boolean
isTest
=
false
;
public
void
testMethod
()
{
try
{
if
(
isTest
)
{
throw
new
IOException
();
}
else
{
throw
new
SQLException
();
}
}
catch
(
IOException
|
SQLException
e
)
{
e
.
printStackTrace
();
}
}
Here are the steps to the exception handling process:
An exception is encountered, which results in an exception object being created.
A new exception object is thrown.
The runtime system looks for code to handle the exception, beginning with the method in which the exception object was created. If no handler is found, the runtime environment traverses the call stack (the ordered list of methods) in reverse looking for an exception handler. If the exception is not handled, the program exits and a stack trace is automatically output.
The runtime system hands the exception object off to an exception handler to handle (catch) the exception.
Programmer-defined exceptions should be created when those other than the existing Java exceptions are necessary. In general, the Java exceptions should be reused wherever possible:
To define a checked exception, the new exception class must extend the Exception
class, directly or indirectly.
To define an unchecked exception, the new exception class must extend the RuntimeException
class, directly or indirectly.
To define an unchecked error, the new error class must extend the Error
class.
User-defined exceptions should have at least two constructors—a constructor that does not accept any arguments and a constructor that does:
public
class
ReportException
extends
Exception
{
public
ReportException
()
{}
public
ReportException
(
String
message
,
int
reportId
)
{
...
}
}
If catching an exception and throwing a more specific exception, it is wise to always capture the base exception.
The methods in the Throwable
class that provide information about thrown exceptions are getMessage()
, toString
, and printStackTrace()
. In general, one of these methods should be called in the catch
clause handling the exception. Programmers can also write code to obtain additional useful information when an exception occurs (i.e., the name of the file that was not found).
This printStackTrace()
method returns a detailed message string about the exception, including its class name and a stack trace from where the error was caught, all the way back to where it was thrown:
try
{
new
FileReader
(
"file.js"
);
}
catch
(
FileNotFoundException
fnfe
)
{
fnfe
.
printStackTrace
();
}
The following is an example of a stack trace. The first line contains the contents returned when the toString()
method is invoked on an exception object. The remainder shows the method calls, beginning with the location where the exception was thrown and going all the way back to where it was caught and handled:
java
.
io
.
FileNotFoundException
:
file
.
js
(
The
system
cannot
find
the
file
specified
)
at
java
.
io
.
FileInputStream
.
open
(
Native
Method
)
at
java
.
io
.
FileInputStream
.(
init
)
(
FileInputSteam
.
java
:
106
)
at
java
.
io
.
FileInputStream
.(
init
)
(
FileInputSteam
.
java
:
66
)
at
java
.
io
.
FileReader
(
init
)(
FileReader
.
java
:
41
)
at
EHExample
.
openFile
(
EHExample
.
java
:
24
)
at
EHExample
.
main
(
EHExample
.
java
:
15
)
Java 9 introduces a Stack-Walking API that allows easy filtering of and lazy access to the information in stack traces.