Exception handling allows us to deal with unexpected situations that may occur in our
programs. As an example, we'll pick opening a file using the FileReader class in the java.io
package. Netbeans will then remind us that this class’s constructor may throw a FileNotFoundException.
And if we try to run the program the compiler will also point this out.
To get rid of this error we must catch the exception using a try-catch statement. This
statement consists of a try block, containing the code that may cause the exceptions, and
one or more catch clauses. If the try block executes successfully the program will then
continue running after the catch blocks, but if an exception occurs execution will then
be passed to the first catch block able to handle that exception type.
In this example the catch block is set to handle only the FileNotFoundException. If
the code in the try block could throw more kinds of exceptions and we wanted to handle
all of them in the same way we could instead catch a more general exception such as the
Exception class itself. This catch clause would then be able to handle all exceptions
that inherit from this class, including the FileNotFoundException. Keep in mind that to
catch a more specific exception we need to place that catch block before more general
exceptions. Note that the catch clause defines an exception object. This object can be used
to obtain more information about the exception, such as a description of the exception using
the getMessage method.
As the last clause in the try-catch statement, we can add a finally block. This block is
used to clean up resources allocated in the try block and will always execute whether
there is an exception or not. In this case we should close the file that we opened in
the try block, but only if it was opened successfully. To be able to access the FileReader object
from the finally clause we must declare it outside of the try block. And because the
close method can also throw an exception we need to surround it with another try-catch
block. Keep in mind that if we forget to close a file the garbage collector will eventually
do it for us, but it’s a good practice to do it ourselves.
When we encounters a situation that a method cannot recover from we can generate our own
exception to signal to the caller that the method has failed. This is done using the
throw keyword followed by a new instance of a Throwable type. Exceptions in Java are grouped
into two categories - checked and unchecked - depending on whether or not they need to
be specified. A method that throws a checked exception, for example the IOException, will
not compile unless it is specified using a throws clause after the method’s parameter
list and the calling method catches the exception. Unchecked exceptions on the other hand, such
as the ArithmeticException, do not have to be caught or specified. Note that to specify
multiple exceptions the exception types are separated with comma.
Exceptions like everything else in Java are classes that exist in a hierarchy. At the
root of this hierarchy we have the Throwable class and all descendants of this class can
be both thrown and caught. Inheriting from Throwable we have the classes Error and Exception.
Classes descending from Error are used to indicate non recoverable exceptions, such
as the OutOfMemoryError. These are unchecked because once they have occurred it is unlikely
that we can do anything about them even if we catch them. Descending from Exception we
have the RuntimeExceptions which are also unchecked. These are exceptions that can occur
in almost any code and it would therefore be cumbersome to catch and specify them. For
example, a division by zero will throw an ArithmeticException, however we wouldn’t
want to have to surround every division operation with a try-catch. There is also an overhead
associated with checking for exceptions and the cost of checking for these exceptions
outweighs the benefit of catching them. Finally, we have the other Exception descendants which
are checked. These are exceptions that we can recover from and that must be both catched
and specified.