|
JavaFAQ Home » Java Lectures by Anatoliy Malyarenko

Advantage 3: grouping error types and error differentiation
Part I was published here.
Often exceptions fall into categories or groups. For example, you could imagine a
group of exceptions, each of which represents a specific type of error that can occur when
manipulating an array: the index is out of range for the size of the array, the element being
inserted into the array is of the wrong type, or the element being searched for is not in the
array. Furthermore, you can imagine that some methods would like to handle all exceptions
that fall within a category (all array exceptions), and other methods would like to handle specific
exceptions (just the invalid index exceptions, please). Because all exceptions that are thrown within a Java program are first-class objects,
grouping or categorisation of exceptions is a natural outcome of the class hierarchy. Java
exceptions must be instances of Throwable or any Throwable descendant. As for other
Java classes, you can create subclasses of the Throwable class and subclasses of your
subclasses. Each "leaf" class (a class with no subclasses) represents a specific type of
exception and each "node" class (a class with one or more subclasses) represents a group
of related exceptions.
For example, in the following diagram, ArrayException is a subclass of Exception (a
subclass of Throwable) and has three subclasses.

InvalidIndexException, ElementTypeException, and NoSuchElementException
are all leaf classes.
Each one represents a specific type of error that can occur when
manipulating an array. One way a method can catch exceptions is to catch only those that
are instances of a leaf class. For example, an exception handler that handles only invalid
index exceptions has a catch statement like this:
| Code: |
catch (InvalidIndexException e) {
. . .
}
|
ArrayException is a node class and represents any error that can occur when
manipulating an array object, including those errors specifically represented by one of its
subclasses. A method can catch an exception based on its group or general type by specifying
any of the exception's superclasses in the catch statement.
For example, to catch all
array exceptions regardless of their specific type, an exception handler would specify an
ArrayException argument:
| Code: |
catch (ArrayException e) {
. . .
}
|
This handler would catch all array exceptions including InvalidIndexException,
ElementTypeException, and NoSuchElementException. You can find out precisely which
type of exception occurred by querying the exception handler parameter e. You could even set
up an exception handler that handles any Exception with this handler:
| Code: |
catch (Exception e) {
. . .
}
|
Exception handlers that are too general, such as the one shown here, can make your
code more error prone by catching and handling exceptions that you didn't anticipate and
therefore are not correctly handled within the handler. We don't recommend writing general
exception handlers as a rule.
As you've seen, you can create groups of exceptions and handle exceptions in a general
fashion, or you can use the specific exception type to differentiate exceptions and handle
exceptions in an exact fashion.
Your first encounter with Java exceptions
The following error message is one of two similar error messages you will see if you try
to compile the class InputFile, because the InputFile class contains calls to methods that
throw exceptions when an error occurs:
InputFile.java:8: Warning: Exception java.io.FileNotFoundException
must be caught, or it must be declared in throws clause of this
method.
in = new FileReader(filename);
The Java language requires that methods either catch or specify all checked exceptions
that can be thrown within the scope of that method. If the compiler detects a method, such as
those in InputFile, that doesn't meet this requirement, it issues an error message like the
one shown above and refuses to compile the program.
Let's look at InputFile in more detail and see what's going on.
The InputFile class wraps a FileReader and provides a method, getWord, for reading
a word from the current position in the reader.
| Code: |
// Note: This class won't compile by design!
import java.io.*;
public class InputFile {
private FileReader in;
public InputFile(String filename) {
in = new FileReader(filename);
}
public String getWord() {
int c;
StringBuffer buf = new StringBuffer();
do {
c = in.read();
if (Character.isWhitespace((char)c))
return buf.toString();
else
buf.append((char)c);
} while (c != -1);
return buf.toString();
}
}
|
The compiler prints the first error message because of the bold line in the above code
listing. The bold line creates a new FileReader object and uses it to open a file whose name
is passed into the FileReader constructor.
So what should the FileReader do if the named file does not exist on the file system?
Well, that depends on what the program using the FileReader wants to do. The implementers
of FileReader have no idea what the InputFile class wants to do if the file does not exist.
Should the FileReader kill the program? Should it try an alternate filename? Should it just
create a file of the indicated name?
There's no possible way the FileReader implementers
could choose a solution that would suit every user of FileReader. So, they punted, or rather,
threw, an exception. If the file named in the argument to the FileReader constructor does
not exist on the file system, the constructor throws a java.io.FileNotFoundException. By
throwing an exception, FileReader allows the calling method to handle the error in whatever
way is most appropriate for it.
As you can see from the code, the InputFile class completely ignores the fact that
the FileReader constructor can throw an exception. However, as stated previously, the Java
language requires that a method either catch or specify all checked exceptions that can be
thrown within the scope of that method. Because the InputFile class does neither, the
compiler refuses to compile the program and prints an error message.
In addition to the first error message shown above, you also see the following similar
error message when you compile the InputFile class:
InputFile.java:15: Warning: Exception java.io.IOException must be
caught, or it must be declared in throws clause of this method.
while ((c = in.read()) != -1) {
The InputFile class's getWord method reads from the FileReader that was opened
in InputFile's constructor. The FileReader read method throws a java.io.IOException
if for some reason it can't read from the file. Again, the InputFile class makes no attempt to
catch or specify this exception. Thus you see the second error message.
At this point, you have two options. You can either arrange to catch the exceptions
within the appropriate methods in the InputFile class, or the InputFile methods can "duck"
and allow other methods further up the call stack to catch them. Either way, the InputFile
methods must do something, either catch or specify the exceptions, before the InputFile
class can be compiled. For the diligent, there's a class, InputFileDeclared, that fixes the
bugs in InputFile by specifying the exceptions. Printer Friendly Page
Send to a Friend
..
Search here again if you need more info!
|