The try Block
In order to catch exceptions thrown by a set of Java
statements, you must first enclose the statements within a try block:
try
{
// Java statements
}
// (Incomplete...)
The catch Block(s)
Following the try block, you may specify a number of
catch blocks:
try
{
// Java statements
}
catch(ExceptionType name)
{
// Java statements
}
catch(ExceptionType name)
{
// Java statements
}
// more catch blocks ...
Each catch block is an exception handler which handles the
type of exception indicated.
Each ExceptionType must be the name of a class that is a
subclass of Throwable.
The name is required, even if you don't use it.
The Java runtime system invokes the appropriate exception
handler when it is the first handler in the call stack whose
ExceptionType matches the type of the exception being thrown.
The system considers it to be a match if the thrown exception
can legally be assigned to the handler's ExceptionType.
Using a Single Handler to Catch Multiple Exception Types
At times, it might seem necessary to have many exception
handlers, each handling a specific exception that your code
throws. This can get pretty unwieldy -- especially if you
have more catch blocks than can fit on a page or screen.
To overcome this problem, try to use the exception
hierarchy to reduce the number of catch blocks you have to
specify. For example, if you're trying to catch all
exceptions that are subclasses of IOException,
all you have to do is:
try
{
// Java statements
}
catch(IOException name)
{
// Java statements
}
If you want to catch a FileNotFoundException
(a subclass of IOException)
specifically, as well as catching all other IOExceptions, then you can do:
try
{
// Java statements
}
catch(FileNotFoundException e)
{
// Java statements
}
catch(IOException e)
{
// Java statements
}
However, when you do such things, it introduces a
catch-block ordering problem. For example, the following will
not work:
try
{
// Java statements
}
catch(IOException e)
{
// Java statements
}
catch(FileNotFoundException e) // WRONG!
{
// Java statements
}
Why not?
The technique of using class inheritance to define
"families" of related exceptions is very useful:
- To organize exceptions in a logical and consistent
way, especially when a family of exceptions relates
to a particular piece of functionality.
- To reduce the number of exceptions that a method
needs to specify.
- To reduce the number of exceptions that need to be
caught explicitly in a try/catch block.
- To allow you to write code without knowing, a priori,
all of the specific exceptions that may be thrown
(you can use the superclass exception name as a
generic name for all exceptions that extend from it,
and later add additional such exceptions.) This is
parrticularly critical when you are writing a
framework to be used and extended by others.
A "catch-all" Block
Unlike C++, which has a try/catch block of the form:
try
{
// C++ statements
}
catch(SpecificException e)
{
// C++ statements
}
catch(...) // Catch any other exceptions
{
// C++ statements
}
Java has no special syntax for a catch block to catch
all (remaining) exceptions. However, you can do the same thing by
specifying Exception (or Throwable) as the ExceptionType in
the catch block:
try
{
// Java statements
}
catch(ExceptionType e)
{
// Java statements
}
catch(Exception e) // Catch any other exceptions
{
// Java statements
}
The following is not
equivalent to the above.
try
{
// Java statements
}
catch(Exception e)
{
// Java statements
}
catch(ExceptionType e)
{
// Java statements
}
Why not?
The finally Block
Optionally, you may specify a finally
block:
try
{
// Java statements
}
catch(ExceptionType1 name)
{
// Java statements
}
catch(ExceptionType2 name)
{
// Java statements
}
// more catch blocks...
finally
{
// Java statements
}
or:
try
{
// Java statements
}
finally
{
// Java statements
}
The statements in the finally block are executed when the
try/catch block exits, whether normally or as a result of
an exception being thrown. This is very useful --
indeed, in some cases indispensable. Here's an example:
Resource res = new Resource();
res.open();
// Do work with res ...
res.close();
This code looks fine, but doesn't work properly when an
exception gets thrown during our work with res. The close() is never executed,
which leaves our resource taking up resources that should
have been cleaned up. Perhaps this will result in a failure
later on during the program's execution.
As one solution to this problem, one could do something
like:
Resource res = new Resource();
res.open();
try
{
// Do work with res ...
res.close();
}
catch(Exception e)
{
res.close();
throw e;
}
but this results in duplicated code, especially when
you're dealing with multiple exceptions. The finally block solution is much
cleaner:
Resource res = new Resource();
res.open();
try
{
// Do work with res ...
}
finally
{
res.close();
}
|