The Artima Developer Community
Sponsored Link

Designing with Exceptions
When and How to Use Exceptions
by Bill Venners
First Published in JavaWorld, June 1998

<<  Page 2 of 5  >>

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.

<<  Page 2 of 5  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us