Exception handling is a plan for changing the flow of control of a program
when an unexpected
event or error occurs. Control is diverted to a part of the program
which can communicate to
the user. Exceptions are thrown by unacceptable conditions, such as
dividing an integer by zero
or trying to access an array element outside of it's defined bounds.
Like most everything in Java, exceptions are handled
using an object-based model. Program
problems are encapsulated in class objects. When
an exception occurs, an Exception object
is created, specifically, one of many specialized
descendants of Throwable, signalling to the
program it has a problem to deal with.(Error
class is a unique branch of Throwable wrapping
problems the programmer doesn't normally have
to deal with.)
The programmer can choose to deal with an exception
using a 'try...catch' construct or it
can pass the exception on up, (the call stack)
all the way to main( ). If main doesn't handle
the exception, the program will halt with
an error report explaining what happened.
The figure below shows the inheritance relations
between Throwable and it's descendants.
The tree describes two branches. There is the
Error branch and the Exception branch. The
Exception branch is subclassed to form the various
checked exceptions. The Exception class
is subclassed by the RuntimeException exception
class which is the parent of the various
runtime exceptions. The hierarchy essentially
describes three groups of exceptions. Errors,
runtime exceptions and checked exceptions. A
more detailed diagram is provided below
the figure. Notice even the detailed diagram
doesn't show all the exception classes used
in the JDK. There are also special Exception
classes (i.e.SQLException) defined inside
packages devoted to particular topics. It makes
sense to house these specialized
Exception class types with the packages that
use them.
Figure 1 The Exception Hierarchy
Object
|
Throwable
/
\
Exception Error
/ \
RuntimeException
The various checked exceptions
/
The various runtime exceptions
Exception Hierarchy Tree
Abreviated from the JDK documentation. The Throwable hierarchy is all in java.lang package
Object | Throwable | |__Error |__Exception | |__ClassNotFoundException |__CloneNotSupportedException |__IllegalAccessException |__InstantiationException |__InterruptedException |__NoSuchFieldException |__NoSuchMethodException | |______RuntimeException | |__ArithmeticException |__ArrayStoreException |__ClassCastException |__IllegalArgumentException | |__IllegalThreadStateException | |__NumberFormatException | |__IllegalMonitorStateException |__IllegalStateException |__IndexOutOfBoundsException | |__ArrayIndexOutOfBoundsException | |__StringIndexOutOfBoundsException | |__NegativeArraySizeException |__NullPointerException |__SecurityException |__UnsupportedOperationException . |
Throwable is parent to Exception
and Error. Exception class includes checked and runtime exceptions
Checked exceptions // errors a programmer can expect to happen in a correct program
Checked exceptions arise in a correct program from problems arising
during a program's
use. A user might make a typing error and enter a number where a letter
should be located.
A power failure might cause an interruption in a network socket connection
causing an
IOException to be thrown. These are types of error a programmer can
expect to occur and
needs to be ready to handle. If a situation arises that may cause a
checked exception to be
raised but the programmer hasn't provided a some code to handle the
exception the compiler
will report the problem to the programmer. For instance the compiler
might report something
like " java.lang.SomeKindOfException must be caught or declared to
be thrown." The
following example won't compile. The compiler reports InterruptedException
must be
caught.
Example
class CheckEx{
public static void main(String
args[]){
Thread.sleep(1000);
}
}
// doesn't compile. reports InterruptedException
Runtime exceptions
Runtime exceptions are thrown by what are commonly called program bugs.
A typical
example of this is when a loop is used that incorrectly references
an array element that
is beyond the range of elements that have been defined. An example
of this would be
accessing the tenth array element in an array that only has nine elements.
This will cause
an ArrayOutOfBounds exception to be thrown. In a correct program, runtime
exceptions
should never occur so you are not required to handle them. It is assumed
the programmer
will fix any problems that cause these runtime exceptions to occur.
In the case of runtime
exceptions, they show up and reported when the program is run. Runtime
exceptions are
often harder to repair as they can result from what is correct code
from the compilers
point of view. The code is correct in the sense it logical leads to
the result that causes the
runtime exception to be thrown. To show the array out of bound problem
the following
sample compiles fine but at runtime throws an ArrayOutOfBounds
exception.
Example
class ArrayBug{
public static void main(String args[]){
float [] f={1.01f };
System.out.println( f[1] );
}
} // compiles but fails to run.
ArrayOutOfBounds exception is thrown
Errors
Errors describe environmental problems which may be rare or difficult
to recover from such
as running out of computer memory. You are not expected to handle
Error class objects.
Hopefully you will never see them.
Error Handling Strategies
There are three stategies one can follow when dealing with exceptions.
The first is more a lack
of stategy, don't do anything at all. It is possible to take this approach
and you get the expected
results. The second is to defer dealing with the exception to a higher
scope. The final stategy is
to use 'try catch and finally blocks to handle the occurence of exceptions.
Do Nothing
The first case scenario in dealing with exceptions is to do absolutely
nothing! The Java compiler
only allows you to get away with this with RunTime and Error exceptions.
This is not a wise
approach but more a demonstration of the simplest scenario. The following
example shows an
exception waiting to happen.
Example
class Trouble{
public static void main(String[] args) {
int x =1 / 0;
System.out.println("We will never see this line printed");
}
} // this will compile
but an exception will be thrown by the runtime environment
Observe the class provides no plan to deal with the inevitable exception
it is set to create. In
this case the exception is thrown in the main( ) method. Since main
is the program's point of
entry, it doesn't have far far to go before being registered as an
exception with the user. The
exception object is created and passed to the runtime which prints
an error report to console
stating an ArithmeticException has been thrown and exits the program.
If you look at detailed
class exception hierarchy described earlier you you will find the RuntimeException
subclass,
ArithmeticException, listed.
User Defined Exceptions and the throw Statement
Before we get to the second stategy we show how a programmer can take
control
of when an exception is thrown using the keyword throw. As well,
we need to
consider that Java is an object-oriented environment. Even exceptions
are defined
as classes that are used as templates to create exception objects.
This opens the
possibility for the java programmer to use inheritance to define custom
exception
classes. Custom exception classes are created by extending Exception
class. The
programmer can use the throw keyword in conjunction with the given
exception
class constructor to generate objects of this class in response to
certain circumstances.
Exceptions are thrown implicitly when something illegal has occured
in a program.
In contrast, exceptions are thrown explicitly with the keyword throw.
In the following example, a custom exception class called PartyException
is created
by extending Exception. This exception object is thrown, in the BirthdayCalendar
call if some birthday condition is met.
Example
class PartyException extends Exception{
PartyException( ){
System.out.println("Time for a party");
}
}
// class PartyException
extends Exception and prints to console its "Time for a party"
class BirthdayCalendar{
public static void main(String[] args)
throws PartyException {
// to get pass the compiler we show main throwing the exception
// Assume we have a list we loop through, itemizing friend
and and family birthdays
// Also assume the current date (plus some advance time) is
compared to the list
// of birthdates. If one is found, a boolean
value, a_birthday, is set to true
if (a_birthday) throw new PartyException( );
}
}
Notice that the throw keyword is used in conjunction with the PartyException
class
constructor and the new operator. There is something else here to note.
Notice we
appended to main the suffix, 'throws PartyException'. This was neccessary
to get the
code to compile. This satisfies the rule that states a method that
may cause an exception
to be thrown must be defined showing explicitely which exception it
can throw. This
leads to the next part of the discussion.
(Notice we didn't have to add a throws clause
to main when we were dealing with
the ArithmeticException. This is ArithmeticException
is a runtime exception. As was
discussed earlier, the programmer is not mandated
to catch runtime exceptions. Not
to say it would not have been a good idea! There
is nothing to stop us from catching
runtime exceptions.
Two Approaches to Handling Exceptions
As mentioned earlier, there are two possible approaches
when dealing with exceptions
in java. We can 'deal' with the exception or
'pass them on'. An analogy can be created
where a clerk is dealing with an irate customer
at a department store. The clerk can
decide to pacify the client or can pass the problem
to higher authority by calling the
manager.
In Java, to pass the problem on involves using
a throws clause. The second approach,
dealing with the problem requires the use of
a try{ }catch( ) finally{ } construct.
The throws Statement
While throw causes an exception to be thrown, the keyword throws
is
appended to
a method's signature to show the method is capable of throwing an exception.
The
throws clause declares that the use of the given method may result
in an exception (of
the type declared) being thrown. This exception, if it is not handled
by appropriate code
locally, will be passed 'up' into the next highest scope containing
this method call.
The following example shows how a throws clause is appended to a method signature.
public static void sleep(long millis) throws InterruptedException
// The sleep( ) method is a static
Thread class method. It has a couple overloaded
// forms. The commonest form
takes an int value representing milliseconds.
Any method that throws an exception (and does
handle the exception itself ) must be itself
declare it throws that exception. In the following
example, the rest method is defined calling
sleep within it. ( Notice it calls the static
method sleep, off of the Thread class name. This is
good form as it signals the reader that sleep
is a static method.)
public
void rest( ) throws InterruptedException{
Thread.sleep(60000);
// sleep might be interrupted and throw InteruptedException
}
Because sleep can throw an InterruptedException,
the compiler now requires the enclosing
method, rest( ), to declare it throws this exception.
If rest( ) is now called in another method
that method too must be declared as throwing InterruptedException and
so on all the way up
to main( ). There is an alternative. The new methods can be written
to handle any exceptions
that occur.
// We see in the excercises main( ) declared as throwing an exception to allow the code to compile
Handling Exceptions
Handling the exception is done in try / catch / finally blocks.
They take the form
try { // block } opt.[ catch ( Exception e ) ] opt.[ finally { // block } ]
1) For each try there has to be at least one catch or
finally statement.
2) There can at most only one finally statement. There
can be any number of catch statements.
3) Exception subclasses must be caught before their more
general parent classes.
The following example shows how the rest( ) method would be rewritten
in order
to handle the exception thrown by sleep( ).
public void rest( ){
try{
Thread.sleep(60000);
}
catch(InterruptedException ie){
System.out.println("Interrupted Exception");
}
Handling Multiple Exceptions
In the following code section of an imaginary
method, a variety of things can happen which
effects the control path taken.
// ...
// somewhere inside a method
//...
try{
// exceptions waiting to happen
}
catch( SubException es){
//
say or do something
}
catch( AnotherException ea){
//
say or do something
}
catch( ParentException ep){
//
say or do something
}
finally{
//
code executed in any case
}
// ...
// the next line following the
finally block of the method
// ...
1) If no exception
occurs, the code in the try block finishes
execution, the finally block
executes and execution resumes
at the line following the finally block.
2) If a SubException
is thrown, execution leaves the try block
at the point the exception
was raised and goes to the
catch block associated with SubException. After this code is
executed, the finally block
is executed and execution proceeds at the line following the
finally block.
3) If a ParentException
is thrown, execution exits the try block
and proceeds at the catch
block associated with the
ParentException. Once this block executes, the finally block
executes and control proceeds
at the line following the finally block.
4) If an unknown
exception occurs, execution proceeds directly
to the finally block. When
the finally block is finished
executing, control leaves the method entirely. This is an uncaught
exeception which will next
appear in the caller.
Snapshot of the Exception class
The Exception class has a couple constuctor forms
and several useful methods which
can be used to augment the information that is
returned when an exception occurs.
For an example look at the end of the assignment
section.
Constructors
public Exception( ) | Constructs an Exception with no specified detail message. |
public Exception(String s) | Constructs an Exception with the specified detail message. |
Methods inherited from Throwable 3 of 7 methods
String
getMessage( ) |
Returns the error message string of this throwable object. |
void
printStackTrace( ) |
Prints this Throwable and
its backtrace to the standard error stream.
// there are two more printStackTrace methods, one prints the stack // trace to a specified print steam and another prints to a PrintWriter |
String
toString( ) |
Returns a short description of this throwable object. |
Use the examples in the note to answer the following questions.
Q1.Write a class with no error protection that throws a divide by zero exception.
Q2.Write an exception class called Flat and throw
it from an if clause with a boolean value,
NoAir. (Don't
use try and catch in this question. This will require declaring main( )
as
throwing the exception.)
Q3 In a try clause throw Exception. In the corresponding
catch clause catch the Exception
object.On catching
it, print to screen "Exception caught", and from a finally block, print
to screen the
word continuing.
Q4 Using the info found at the top of page 193,
'Just Java', add a string, "probably a nail" to
the Flat exception
you wrote earlier via the constructor and using super( ) so that when
the getMessage(
) method is called on a Flat object, this string is also printed to screen.
When Flat extends the Exception
class, give it a constructor using the form below where it takes
a string.
Then invoke the parent constructor,
in the first line of code using super(s), where s is the String argument
identifier of the Flat constructor.
For example
class MotorFailure extends Exception{
MotorFailure(String
cause){
super(cause);
}
}
// should really add the no-args constructor as well now in case someone
extends this class
Now this exception class can be
instantiated with the constructor that takes a descriptive string. This
string is returned if getMessage(
) is called on the exception object thrown.
For example
class MotorTest{
public static void main(String[]args){
try {
throw new MotorFailure("blown
gasket");
}
catch(MotorFailure mf){
System.out.println( mf.getMessage( ));
}
}
}