|
|
|
Advertisement
|
When to throw exceptions
When should you throw an exception? The answer can be summed up in one
guideline:
If your method encounters an abnormal condition that it can't handle, it should throw an exception.
Unfortunately, though this guideline may be easy to memorize and may sound impressive when you recite it at parties, it doesn't clear up the picture too much. It actually leads to a different question: What is an "abnormal condition?"
That, it turns out, is the $64,000 question. Deciding whether or not a particular event qualifies as an "abnormal condition" is a subjective judgment. The decision is not always obvious. It's one reason they pay you the big bucks.
A more helpful rule of thumb could be:
Avoid using exceptions to indicate conditions that can reasonably be expected as part of the typical functioning of the method.
An abnormal condition, therefore, would be any condition that wouldn't reasonably be expected as part of the "normal functioning" of a method. To help you get a feel for what I mean by "normal functioning of a method," allow me to give a few examples.
A few examples
As an illustration, consider the FileInputStream and
DataInputStream classes from the java.io
package. Here is an application that uses FileInputStream
to print the text of a file to the standard output:
// In source packet in file except/ex9/Example9a.java
import java.io.*;
class Example9a {
public static void main(String[] args)
throws IOException {
if (args.length == 0) {
System.out.println("Must give filename as first arg.");
return;
}
FileInputStream in;
try {
in = new FileInputStream(args[0]);
}
catch (FileNotFoundException e) {
System.out.println("Can't find file: " + args[0]);
return;
}
int ch;
while ((ch = in.read()) != -1) {
System.out.print((char) ch);
}
System.out.println();
in.close();
}
}
This example shows that the read() method of
FileInputStream reports an "end of file has been reached"
condition not by throwing an exception, but by returning a special
value: -1. In this method, reaching end of file is considered a
"normal" part of using the method. It is not considered an "abnormal"
condition. The usual way to read bytes is to keep on reading them until
you hit the end.
The DataInputStream class, on the other hand, takes a
different approach when reporting end of file:
// In source packet in file except/ex9b/Example9b.java
import java.io.*;
class Example9b {
public static void main(String[] args)
throws IOException {
if (args.length == 0) {
System.out.println("Must give filename as first arg.");
return;
}
FileInputStream fin;
try {
fin = new FileInputStream(args[0]);
}
catch (FileNotFoundException e) {
System.out.println("Can't find file: " + args[0]);
return;
}
DataInputStream din = new DataInputStream(fin);
try {
int i;
for (;;) {
i = din.readInt();
System.out.println(i);
}
}
catch (EOFException e) {
}
fin.close();
}
}
Each time the readInt() method of
DataInputStream is invoked, it reads four bytes from the
stream and interprets them as an int. When
readInt() encounters end of file, it throws
EOFException.
Throwing an exception is a reasonable approach for this method for two
reasons. First, readInt() can't return a special value to
indicate end of file, because all possible return values are valid
ints. (It can't return -1 on end of file, for example,
because it may read a -1 from the stream and need to return it as a
valid int value.) Second, if readInt()
encounters end of file after reading only one, two, or three bytes,
that probably qualifies as an "abnormal condition." The method is
supposed to read four bytes, but only one to three are available. Given
that this exception is an integral part of using this class, it is a
checked exception (a subclass of Exception). Client
programmers are forced to deal with it.
A third approach to signaling an "end has been reached" condition is
illustrated by the StringTokenizer and Stack
classes in the following example:
// In source packet in file except/ex9b/Example9c.java
// This program prints the white-space separated tokens of an
// ASCII file in reverse order of their appearance in the file.
import java.io.*;
import java.util.*;
class Example9c {
public static void main(String[] args)
throws IOException {
if (args.length == 0) {
System.out.println("Must give filename as first arg.");
return;
}
FileInputStream in = null;
try {
in = new FileInputStream(args[0]);
}
catch (FileNotFoundException e) {
System.out.println("Can't find file: " + args[0]);
return;
}
// Read file into a StringBuffer
StringBuffer buf = new StringBuffer();
try {
int ch;
while ((ch = in.read()) != -1) {
buf.append((char) ch);
}
}
finally {
in.close();
}
// Separate StringBuffer into tokens and
// push each token into a Stack
StringTokenizer tok = new StringTokenizer(buf.toString());
Stack stack = new Stack();
while (tok.hasMoreTokens()) {
stack.push(tok.nextToken());
}
// Print out tokens in reverse order.
while (!stack.empty()) {
System.out.println((String) stack.pop());
}
}
}
This example reads in the bytes of a file, converts them to
chars, and places the chars into a
StringBuffer. It then uses a StringTokenizer
to extract one white-space separated token (a String) at a
time and push it onto a Stack. Next it pops all tokens
from the Stack and prints them out one per line. Because
Stack implements a Last In First Out (LIFO) stack, the
tokens are printed in reverse order from their appearance in the file.
Both the StringTokenizer and the Stack must
signal an "end has been reached" condition. The
StringTokenizer constructor takes as a parameter the
source String to tokenize. Each invocation of
nextToken() returns a String that represents
the next token of the source String. Eventually, all the
tokens in the source String will be consumed, and
StringTokenizer must somehow indicate that the end of
tokens has been reached. In this case, there is a special return value,
null, that could have been used to indicate the end of
tokens. But the designer of this class took a different approach. A
separate method, hasMoreTokens(), returns a
boolean value indicating whether or not the end of tokens
has been reached. You must invoke hasMoreTokens() each
time you invoke nextToken().
This approach shows that the designer did not consider reaching the end
of tokens an abnormal condition. It is a normal way to use the class.
After the end has been reached, however, if you don't check
hasMoreTokens() and call
nextToken(), you
will be rewarded with the NoSuchElementException. Although
this exception is thrown on an end of tokens condition, it is an
unchecked exception (a subclass of RuntimeException). It
is thrown more to indicate a software bug -- that you are not using the
class correctly -- than to indicate the end of tokens condition.
Similarly, the Stack class has a method,
empty(), that returns a boolean to indicate
that the last object has been popped from the stack. You must invoke
empty() each time you invoke pop(). If you
neglect to invoke empty()and invoke pop() on
an empty stack, you get an EmptyStackException. Although
this exception is thrown when an "end of objects on the stack"
condition is encountered, it is another unchecked runtime exception. It
is intended to be more an indication of a software bug in the client
code (the improper use of the Stack class) than the normal
way to detect an empty stack.
|
Sponsored Links
|