Chapter 12. Exceptions and Assertions

 

A slipping gear could let your M203 grenade launcher fire when you least expect it. That would make you quite unpopular in what's left of your unit.

 
 --The U.S. Army's PS magazine, August 1993

During execution, applications can run into many kinds of errors of varying degrees of severity. When methods are invoked on an object, the object can discover internal state problems (inconsistent values of variables), detect errors with objects or data it manipulates (such as a file or network address), determine that it is violating its basic contract (such as reading data from an already closed stream), and so on.

Many programmers do not test for all possible error conditions, and for good reason: Code becomes unintelligible if each method invocation checks for all possible errors before the next statement is executed. This trade-off creates a tension between correctness (checking for all errors) and clarity (not cluttering the basic flow of code with many error checks).

Exceptions provide a clean way to check for errors without cluttering code. Exceptions also provide a mechanism to signal errors directly rather than indirectly with flags or side effects such as fields that must be checked. Exceptions make the error conditions that a method can signal an explicit part of the method's contract. The list of exceptions can be seen by the programmer, checked by the compiler, and preserved (if needed) by extended classes that override the method.

An exception is thrown when an unexpected error condition is encountered. The exception is then caught by an encompassing clause further up the method invocation stack. Uncaught exceptions result in the termination of the thread of execution, but before it terminates, the thread's UncaughtExceptionHandler is given the opportunity to handle the exception as best it can, usually printing useful information (such as a call stack) about where the exception was thrown—see “Threads and Exceptions” on page 379.

Creating Exception Types

Exceptions are objects. All exception types—that is, any class designed for throwable objects—must extend the class Throwable or one of its subclasses. The Throwable class contains a string that can be used to describe the exception—it is set via a constructor parameter and may be retrieved with the getMessage method. By convention, most new exception types extend Exception, a subclass of Throwable. Exception types are not allowed to be generic types.

Exceptions are primarily checked exceptions, meaning that the compiler checks that your methods throw only the exceptions they have declared themselves to throw. The standard runtime exceptions and errors extend one of the classes RuntimeException and Error, making them unchecked exceptions. Here is the top of the exception type hierarchy:

Creating Exception Types

Checked exceptions represent conditions that, although exceptional, can reasonably be expected to occur, and if they do occur must be dealt with in some way. Making these exceptions checked documents the existence of the exception and ensures that the caller of a method deals with the exception in some way—or at least consciously chooses to ignore it.

Unchecked runtime exceptions represent conditions that, generally speaking, reflect errors in your program's logic and cannot be reasonably recovered from at run time. For example, the ArrayIndexOutOfBoundsException thrown when you access outside the bounds of an array tells you that your program calculated an index incorrectly, or failed to verify a value to be used as an index. These are errors that should be corrected in the program code. Given that you can make errors writing any statement it would be totally impractical to have to declare or catch all the exceptions that could arise from those errors—hence they are unchecked.

The Error class, through its subclasses, defines a range of errors that indicate something has failed in the virtual machine itself (VirtualMachineError), or in the virtual machine's attempt to execute your application (LinkageError). These are also unchecked because they are beyond the applications ability to control, or handle. Application code should rarely, if ever, throw these error exceptions directly.

Nearly all exceptions you create should extend Exception, making them checked exceptions. These new checked exceptions represent the exceptional conditions that can arise in your library or application. You can usually handle any runtime problems that arise in your code by throwing one of the existing runtime exception classes—there are sufficient of these that one of them is usually a good match to the kind of error that has occurred, or else the error is sufficiently general that throwing an instance of RuntimeException itself is sufficient. Occasionally, you may want to extend an existing runtime exception class to provide additional information about the error that occurred. Even more rarely, you might define a new class of runtime exception that is specific to your application domain. Just remember that when it comes to unchecked exceptions, proper usage relies on good documentation because the compiler will not be able to assist you.

Sometimes it is useful to have more data to describe the exceptional condition than what is in the Exception class. In such cases, you can extend Exception to create a class that contains the added data (usually set in the constructor).

For example, suppose a replaceValue method is added to the Attributed interface discussed in Chapter 4. This method replaces the current value of a named attribute with a new value. If the named attribute doesn't exist, an exception should be thrown, because it is reasonable to assume that one should replace only existing attributes. That exception should contain the name of the attribute. To represent the exception, we create the NoSuchAttributeException class:

public class NoSuchAttributeException extends Exception {
    public final String attrName;

    public NoSuchAttributeException(String name) {
        super("No attribute named "" + name + "" found");
        attrName = name;
    }
}

NoSuchAttributeException extends Exception to add a constructor that takes the name of the attribute; it also adds a public final field to store the data. The constructor invokes the superclass's constructor with a string description of what happened. This custom exception type helps when writing code that catches the exception because it holds both a human-usable description of the error and the data that created the error. Adding useful data is one reason to create a new exception type.

Another reason to create a new exception type is that the type of the exception is an important part of the exception data, because exceptions are caught according to their type. For this reason, you would invent NoSuchAttributeException even if you did not want to add data. In this way, a programmer who cared only about such an exception could catch it exclusive of other exceptions that might be generated either by the methods of the Attributed interface, or by other methods used on other objects in the same area of code.

In general, new exception types should be created when programmers will want to handle one kind of error differently from another kind. Programmers can then use the exception type to execute the correct code rather than examine the contents of the exception to determine whether they really care about the exception or they have caught an irrelevant exception by accident.

throw

You throw exceptions using the throw statement:

throw expression;

where expression must evaluate to a value or variable that is assignable to Throwable—or in simple terms, a reference to a Throwable object. For example, here is an addition to AttributedImpl from Chapter 4 that implements replaceValue:

public void replaceValue(String name, Object newValue)
    throws NoSuchAttributeException
{
    Attr attr = find(name);         // look up the attr
    if (attr == null)               // it isn't found
        throw new NoSuchAttributeException(name);
    attr.setValue(newValue);
}

The replaceValue method first looks up the current Attr object for the name. If there isn't one, it throws a new object of type NoSuchAttributeException, providing the constructor with the attribute name. Exceptions are objects, so they must be created before being thrown. If the attribute does exist, its value is replaced with the new value. You can also generate an exception by invoking a method that itself throws an exception.

Transfer of Control

When an exception is thrown, the statement or expression that caused the exception is said to complete abruptly. This abrupt completion of statements causes the call chain to gradually unwind as each block or method invocation completes abruptly until the exception is caught. If the exception is not caught, the thread of execution terminates, after giving the thread's UncaughtExceptionHandler a chance to handle the exception—see “Threads and Exceptions” on page 379.

Once an exception occurs, actions after the point at which the exception occurred do not take place. If evaluation of a left-operand causes an exception then no part of the right-operand is evaluated; if evaluation of a left argument expression results in an exception, then no argument to the right is evaluated. The next action to occur will be in either a finally block, or a catch block that catches the exception.

Asynchronous Exceptions

A throw statement results in a synchronous exception, as does, say, a divide-by-zero arithmetic exception—the exception occurs directly as a result of executing a particular instruction; either the throw or dividing by zero. In contrast an asynchronous exception is one that can occur at any time, regardless of the instructions being executed.

Asynchronous exceptions can occur in only two specific ways. The first is an internal error in the Java virtual machine—such exceptions are considered asynchronous because they are caused by the execution of instructions in the virtual machine, not the instructions of the program. Needless to say, there is little that you can do about an internal error.

The second mechanism is the use of the deprecated Thread.stop methods, or the related, and not deprecated, stopThread methods of the JVM Tool Interface (JVMTI)—a native code interface to the virtual machine that allows for the inspection, and control of, a running application. These methods allow an asynchronous exception of any kind (checked or unchecked) to be thrown at any point during the execution of the target thread. Such a mechanism is inherently dangerous, which is why it has been deprecated in the Thread class—we discuss this further in Chapter 14.

The throws Clause

The definition of the replaceValue method declares which checked exceptions it throws. The language requires such a declaration because programmers invoking a method need to know the exceptions it can throw just as much as they need to know its normal behavior. The checked exceptions that a method throws are as important as the type of value it returns. Both must be declared.

The checked exceptions a method can throw are declared with a throws clause, which declares a comma-separated list of exception types. Only those exceptions that are not caught within the method must be listed.

You can throw checked exceptions that are extensions of any type of exception in the throws clause because you can use a class polymorphically anywhere its superclass is expected. A method can throw many classes of checked exceptions—all of them extensions of a particular exception class—and declare only the superclass in the throws clause. By doing so, however, you hide potentially useful information from programmers invoking the method: They won't know which of the possible extended exception types could be thrown. For documentation purposes, the throws clause should be as complete and specific as possible.

The contract defined by the throws clause is strictly enforced—you can throw only a type of checked exception that has been declared in the throws clause. Throwing any other type of checked exception is invalid, whether you use throw directly or use it indirectly by invoking another method. If a method has no throws clause, that does not mean that any exceptions can be thrown: It means that no checked exceptions can be thrown.

All the standard runtime exceptions (such as ClassCastException and ArithmeticException) are extensions of the RuntimeException class. The more serious errors are signaled by exceptions that are extensions of Error, and these exceptions can occur at any time in any code. RuntimeException and Error are the only exceptions you do not need to list in your throws clauses. They are ubiquitous, and every method can potentially throw them. This is why they are unchecked by the compiler. Those runtime exceptions—such as IllegalArgumentException—thrown under specific conditions should always be documented, even though they do not appear in the throws clause; see Chapter 19 for details on how to document exceptions thrown by your methods.

Because checked exceptions must be declared in a throws clause, it follows that any code fragment outside a method, or constructor, with a throws clause cannot throw a checked exception. This means that static initializers and static initialization blocks cannot throw checked exceptions, either directly or by invoking a method that throws such an exception. Non-static initializers and non-static initialization blocks are considered to be part of the constructor for a class, and so they are allowed to throw checked exceptions only if all the constructors of the class declare those checked exceptions.

Checked exception handling is strictly enforced because doing so helps avoid bugs that come from not dealing with errors. Experience has shown that programmers forget to handle errors or defer writing code to handle them until some future time that never arrives. The throws clause states clearly which exceptions are being thrown by methods and makes sure they are dealt with in some way by the invoker.

If you invoke a method that lists a checked exception in its throws clause, you have three choices:

  • Catch the exception and handle it.

  • Catch the exception and map it into one of your exceptions by throwing an exception of a type declared in your own throws clause.

  • Declare the exception in your throws clause and let the exception pass through your method (although you might have a finally clause that cleans up first; see “finally” on page 288 for details).

The first two choices require that you catch exceptions thrown by other methods—something you will learn about soon.

You should be explicit in your throws clause, listing all the exceptions you know that you throw, even when you could encompass several exceptions under some superclass they all share. This is good self-documentation. Deciding how explicit you should be requires some thought. If you are designing a general interface or superclass you have to think about how restrictive you want to be to the implementing classes. It may be quite reasonable to define a general exception you use in the interface's throws clause, and to expect that the implementing classes will be more specific where possible. This tactic is used by the java.io package, which defines a general IOException type for its methods to throw. This lets implementing classes throw exceptions specific to whatever kind of I/O is being done. For example, the classes that do I/O across network channels can throw various network-related subclasses of IOException, and those dealing with files throw file-related subclasses.

throws Clauses and Method Overriding

When you override an inherited method, or implement an inherited abstract method, the throws clause of the overriding method must be compatible with the throws clause of the inherited method (whether abstract or not).

The simple rule is that an overriding or implementing method is not allowed to declare more checked exceptions in the throws clause than the inherited method does. The reason for this rule is quite simple: Code written to deal with the original method declaration won't be prepared to catch any additional checked exceptions and so no such exceptions are allowed to be thrown. Subtypes of the declared exceptions can be thrown because they will be caught in a catch block for their supertype. If the overriding or implementing method does not throw a checked exception then it need not redeclare that exception. For example, as you saw in “Strategies for Cloning” on page 101, a class that implements Cloneable need not declare that clone may throw a CloneNotSupportedException. Whether to declare it or not is a matter of design—if you declare it in the overriding method then subclasses of your class will be allowed to throw the exception in that method, otherwise they will not.

If a method declaration is multiply inherited—that is, it exists in more than one inherited interface, or in both an inherited interface and a superclass—then the throws clause of that method must satisfy all the inherited throws clauses. As we discussed in “Inheriting, Overriding, and Overloading Methods” on page 125, the real issue in such multiple inheritance situations, is whether a single implementation of a method can honor all the inherited contracts.

throws Clauses and Native Methods

A native method declaration (see page 74) can provide a throws clause that forces all users of that method to catch or redeclare the specified checked exceptions. However, the implementation of native methods is beyond the control of the Java compiler and so they cannot be checked to ensure that only the declared exceptions are thrown. Well-written native methods, however, will throw only those checked exceptions that they declare.

Exercise 12.1Create an ObjectNotFoundException class for the LinkedList class you built in previous exercises. Add a find method that looks for an object in the list and either returns the LinkedList object that contains the desired object or throws the exception if the object isn't found in the list. Why is this preferable to returning null if the object isn't found? What additional data if any should ObjectNotFoundException contain?

try, catch, and finally

You catch exceptions by enclosing code in try blocks. The basic syntax for a try block is:

try {
    statements
} catch (exception_type1 identifier1) {
    statements
} catch (exception_type2 identifier2) {
    statements
...
} finally {
    statements
}

where either at least one catch clause, or the finally clause, must be present. The body of the try statement is executed until either an exception is thrown or the body finishes successfully. If an exception is thrown, each catch clause is examined in turn, from first to last, to see whether the type of the exception object is assignable to the type declared in the catch. When an assignable catch clause is found, its block is executed with its identifier set to reference the exception object. No other catch clause will be executed. Any number of catch clauses, including zero, can be associated with a particular try as long as each clause catches a different type of exception. If no appropriate catch is found, the exception percolates out of the try statement into any outer try that might have a catch clause to handle it.

If a finally clause is present with a try, its code is executed after all other processing in the try is complete. This happens no matter how completion was achieved, whether normally, through an exception, or through a control flow statement such as return or break.

This example code is prepared to handle one of the exceptions replaceValue throws:

Object value = new Integer(8);
try {
    attributedObj.replaceValue("Age", value);
} catch (NoSuchAttributeException e) {
    // shouldn't happen, but recover if it does
    Attr attr = new Attr(e.attrName, value);
    attributedObj.add(attr);
}

The try sets up a statement (which must be a block) that does something that is normally expected to succeed. If everything succeeds, the block is finished. If any exception is thrown during execution of the code in the try block, either directly via a throw or indirectly by a method invoked inside it, execution of the code inside the try stops, and the attached catch clause is examined to see whether it wants to catch the exception that was thrown.

A catch clause is somewhat like an embedded method that has one parameter — namely, the exception to be caught. As with method parameters, the exception “parameter” can be declared final, or can have annotations applied. Inside a catch clause, you can attempt to recover from the exception, or you can clean up and rethrow the exception so that any code calling yours also has a chance to catch it. Or a catch can do what it needs to and then fall out the bottom, in which case control flows to the statement after the try statement (after executing the finally clause, if there is one).

A general catch clause—one that catches exceptions of type Exception, for example—is usually a poor implementation choice since it will catch any exception, not just the specific one you are interested in. Had we used such a clause in our code, it could have ended up handling, for example, a ClassCastException as if it were a missing attribute problem.

You cannot put a superclass catch clause before a catch of one of its subclasses. The catch clauses are examined in order, so a catch that picked up one exception type before a catch for an extended type of exception would be a mistake. The first clause would always catch the exception, and the second clause would never be reached. The compiler will not accept the following code:

class SuperException extends Exception { }
class SubException extends SuperException { }

class BadCatch {
    public void goodTry() {
        /* This is an INVALID catch ordering */
        try {
            throw new SubException();
        } catch (SuperException superRef) {
            // Catches both SuperException and SubException
        } catch (SubException subRef) {
            // This would never be reached
        }
    }
}

Only one exception is handled by any single encounter with a try clause. If a catch or finally clause throws another exception, the catch clauses of the try are not reexamined. The catch and finally clauses are outside the protection of the try clause itself. Such exceptions can, of course, be handled by any encompassing try block in which the inner catch or finally clauses were nested.

finally

The finally clause of a try statement provides a mechanism for executing a section of code whether or not an exception is thrown. Usually, the finally clause is used to clean up internal state or to release non-object resources, such as open files stored in local variables. Here is a method that closes a file when its work is done, even if an error occurs:

public boolean searchFor(String file, String word)
    throws StreamException
{
    Stream input = null;
    try {
        input = new Stream(file);
        while (!input.eof())
            if (input.next().equals(word))
                return true;
        return false;       // not found
    } finally {
        if (input != null)
            input.close();
    }
}

If the new fails, input will never be changed from its initial null value. If the new succeeds, input will reference the object that represents the open file. When the finally clause is executed, the input stream is closed only if it has been open. Whether or not the operations on the stream generate an exception, the contents of the finally clause ensure that the file is closed, thereby conserving the limited resource of simultaneous open files. The searchFor method declares that it throws StreamException so that any exceptions generated are passed through to the invoking code after cleanup, including any StreamException thrown by the invocation of close.

There are two main coding idioms for correctly using finally. The general situation is that we have two actions, call them pre and post, such that if pre occurs then post must occur—regardless of what other actions occur between pre and post and regardless of whether those actions complete successfully or throw exceptions. One idiom for ensuring this is:

pre();
try {
    // other actions
} finally {
    post();
}

If pre succeeds then we enter the try block and no matter what occurs we are guaranteed that post gets executed. Conversely, if pre itself fails for some reason and throws an exception, then post does not get executed—it is important that pre occur outside the try block in this situation because post must not execute if pre fails.

You saw the second form of the idiom in the stream searching example. In that case pre returns a value that can be used to determine whether or not it completed successfully. Only if pre completed successfully is post invoked in the finally clause:

Object val = null;
try {
    val = pre();
    // other actions
} finally {
    if (val != null) 
        post();
}

In this case, we could still invoke pre outside the try block, and then we would not need the if statement in the finally clause. The advantage of placing pre inside the try block comes when we want to catch both the exceptions that may be thrown by pre and those that may be thrown by the other actions—with pre inside the try block we can have one set of catch blocks, but if pre were outside the try block we would need to use an outer try-catch block to catch the exceptions from pre. Having nested try blocks can be further complicated if both pre and the other actions can throw the same exceptions—quite common with I/O operations—and we wish to propagate the exception after using it in some way; an exception thrown by the other actions would get caught twice and we would have to code our catch blocks to watch for and deal with that situation.

A finally clause can also be used to clean up for break, continue, and return, which is one reason you will sometimes see a try clause with no catch clauses. When any control transfer statement is executed, all relevant finally clauses are executed. There is no way to leave a try block without executing its finally clause.

The preceding example relies on finally in this way to clean up even with a normal return. One of the most common reasons goto is used in other languages is to ensure that certain things are cleaned up when a block of code is complete, whether or not it was successful. In our example, the finally clause ensures that the file is closed when either the return statement is executed or the stream throws an exception.

A finally clause is always entered with a reason. That reason may be that the try code finished normally, that it executed a control flow statement such as return, or that an exception was thrown in code executed in the try block. The reason is remembered when the finally clause exits by falling out the bottom. However, if the finally block creates its own reason to leave by executing a control flow statement (such as break or return) or by throwing an exception, that reason supersedes the original one, and the original reason is forgotten. For example, consider the following code:

try {
    // ... do something ...
    return 1;
} finally {
    return 2;
}

When the try block executes its return, the finally block is entered with the “reason” of returning the value 1. However, inside the finally block the value 2 is returned, so the initial intention is forgotten. In fact, if any of the other code in the try block had thrown an exception, the result would still be to return 2. If the finally block did not return a value but simply fell out the bottom, the “return the value 1” reason would be remembered and carried out.

Exception Chaining

Exceptions sometimes are caused by other exceptions. In “A Quick Tour”, Section 1.14 on page 32, you saw an example where this was true:

public double[] getDataSet(String setName)
    throws BadDataSetException
{
    String file = setName + ".dset";
    FileInputStream in = null;
    try {
        in = new FileInputStream(file);
        return readDataSet(in);
    } catch (IOException e) {
        throw new BadDataSetException();
    } finally {
        try {
            if (in != null)
                in.close();
        } catch (IOException e) {
            ;    // ignore: we either read the data OK
                 // or we're throwing BadDataSetException
        }
    }
}
// ... definition of readDataSet ...

This method throws a BadDataSetException on any I/O exception or data format error. The problem is that any information about the original exception is lost, and it might be a needed clue to fixing the problem.

Replacing exceptions with other ones is an important way to raise the level of abstraction. To any code invoking the above method, all failures can be recovered from in the same way. The particulars of the failure probably don't much matter to what the program will do, which is to handle the failure of the data file. But humans fixing the problem can require knowledge of the exact failure, so they will want to have that information available—see “Stack Traces” on page 294.

Situations like this are common enough that the exception mechanism includes the notion of one exception being caused by another exception. The initCause method, defined in Throwable, sets one exception's cause to be the exception object passed as a parameter. For example, the previous example can have its IOException catch clause rewritten as:

} catch (IOException e) {
    BadDataSetException bdse = new BadDataSetException();
    bdse.initCause(e);
    throw bdse;
} finally {
    // ...
}

Here initCause is used to remember the exception that made the data bad. This means that the invoking code can handle all bad data simply with one exception handler but still know the kind of exception that caused the underlying problem. The invoking code can use the getCause method to retrieve the exception.

The example can be simplified further by writing BadDataSetException to expect to have a cause, at least some of the time, and so provide a constructor to accept that cause if it is known. The idiomatic way to define a new exception class is to provide at least the following four constructor forms—or variants thereof that deal with exception specific data:

class BadDataSetException extends Exception {
    public BadDataSetException() {}

    public BadDataSetException(String details) {
        super(details);
    }

    public BadDataSetException(Throwable cause) {
        super(cause);
    }

    public BadDataSetException(String details,
                               Throwable cause) {
        super(details, cause);
    }
}

Now the catch clause in the example simply becomes:

} catch (IOException e) {
    throw new BadDataSetException(e);
} finally {
    // ...
}

Not all exception classes provide cause-taking constructors, but all support the initCause method. To make it easier to use initCause with such exception classes, it returns the exception instance that it is invoked on. The idea is that you can use it like this:

throw new BadDataSetException().initCause(e); // Error?

The only problem is that this will nearly always result in a compile-time error: initCause returns a Throwable instance, and you can only throw a Throwable if your method's throw clause lists Throwable—which it rarely, if ever, should! Consequently, you have to modify the above, to cast the Throwable back to the actual exception type you are creating:

throw (BadDataSetException)
    new BadDataSetException().initCause(e);

Note that an exception can only have it's cause set once—either via a constructor, or by a single call to initCause—any attempt to set it again will cause an IllegalStateException to be thrown.

Stack Traces

When an exception is created, a stack trace of the call is saved in the exception object. This is done by the Throwable constructor invoking its own fillInStacktrace method. You can print this stack trace by using printStackTrace, and you can replace it with the current stack information by invoking fillInStackTrace again. If an exception has a cause, then typically printStackTrace will print the current exceptions stack trace, followed by the stack trace of its cause—however, the details of stack trace printing depend on the virtual-machine implementation.

A stack trace is represented by an array of StackTraceElement objects that you can get from getStackTrace. You can use this array to examine the stack or to create your own display of the information. Each StackTraceElement object represents one method invocation on the call stack. You can query these with the methods getFileName, getClassName, getMethodName, getLineNumber, and isNativeMethod.

You can also set the stack trace with setStackTrace, but you would have to be writing a very unusual program for this to be a good idea. The real information contained in a stack trace is quite valuable. Do not discard it without compelling need. Proper reasons for changing a stack trace are vital, but very rare.

When to Use Exceptions

We used the phrase “unexpected error condition” at the beginning of this chapter when describing when to throw exceptions. Exceptions are not meant for simple, expected situations. For example, reaching the end of a stream of input is expected, so the method that returns the next input from the stream has “hitting the end” as part of its expected behavior. A return flag that signals the end of input is reasonable because it is easy for callers to check the return value, and such a convention is also easier to understand. Consider the following typical loop that uses a return flag:

while ((token = stream.next()) != Stream.END)
    process(token);
stream.close();

Compare that to this loop, which relies on an exception to signal the end of input:

try {
    for (;;) {
        process(stream.next());
    }
} catch (StreamEndException e) {
    stream.close();
}

In the first case, the flow of control is direct and clear. The code loops until it reaches the end of the stream, and then it closes the stream. In the second case, the code seems to loop forever. Unless you know that end of input is signaled with a StreamEndException, you don't know the loop's natural range. Even when you know about StreamEndException, this construction can be confusing since it moves the loop termination from inside the for loop into the surrounding try block.

In some situations no reasonable flag value exists. For example, a class for a stream of double values can contain any valid double, and there is no possible end-of-stream marker. The most reasonable design is to add an explicit eof test method that should be called before any read from the stream:

while (!stream.eof())
    process(stream.nextDouble());
stream.close();

On the other hand, continuing to read past the end of input is not expected. It means that the program didn't notice the end and is trying to do something it should never attempt. This is an excellent case for a ReadPastEndException. Such behavior is outside the expected use of your stream class, and throwing an exception is the right way to handle it.

Deciding which situations are expected and which are not is a fuzzy area. The point is not to abuse exceptions as a way to report expected situations.

Exercise 12.2Decide which way the following conditions should be communicated to the programmer:

  • Someone tries to set the capacity of a PassengerVehicle object to a negative value.

  • A syntax error is found in a configuration file that an object uses to set its initial state.

  • A method that searches for a programmer-specified word in a string array cannot find any occurrence of the word.

  • A file provided to an “open” method does not exist.

  • A file provided to an “open” method exists, but security prevents the user from using it.

  • During an attempt to open a network connection to a remote server process, the remote machine cannot be contacted.

  • In the middle of a conversation with a remote server process, the network connection stops operating.

Assertions

An assertion is used to check an invariant, a condition that should always be true. If the assertion is found to be false then an exception is thrown. If your code assumes that something is true, adding an assertion to test it gives you a way to catch mistakes before they cause odd effects. For example, in a linked list the last element in the list usually has a null reference to the next element:

public void append(Object value) {
    ListNode node = new ListNode(value);
    if (tail != null)
        tail.next = node;
    tail = node;
    assert tail.next == null;
}

When the append method has done its work, the assert double-checks that the last node is properly formed. If it is not, then an AssertionError is thrown.

By default, assertions are not evaluated. It is possible to turn assertion evaluation on and off for packages, classes, and entire class loaders, as you will soon see. When assertions are turned off, the assertions are not evaluated at all. This means that you must be careful about side effects that affect non-assertion code. For example, the following code is very dangerous:

assert ++i < max;

When assertions are being evaluated, the value of i will be incremented to the next value. But when someone turns off assertions the entire expression will logically vanish from the code leaving i perpetually unchanged. Don't ever do anything like this. Split it up:

i++;
assert i < max;

The assert Statement

The syntax for an assertion is:

assert eval-expr [: detail-expr];

where eval-expr is either a boolean or Boolean expression and detail-expr is an optional expression that will be passed to the AssertionError constructor to help describe the problem. The detail expression is optional; if present, it provides information to a person who looks at the statement or thrown exception. If the detail is a Throwable it will become the cause of the AssertionError; that is, it will be the value returned by the error's getCause method. Otherwise, the detail will be converted to a string and become the detail message for the error.

When an assert is encountered in the code, the first expression is evaluated. If the resulting value is true, the assertion passes. If it is false, the assertion fails, and an AssertionError will be constructed and thrown.

When to Use Assertions

Assertions are typically used to make sure those things that “can't happen” are noticed if they do happen. This is why a failed assertion throws a kind of Error instead of an Exception. A failed assertion means that the current state is basically insane.

You should not use assertions to test for things that you know will actually happen sometimes (IOException) or for which other means are common (IllegalArgumentException, NullPointerException, …). You want to deal with these problems whether or not assertions are being evaluated. Assertions are for testing for things that should never happen—in a properly functioning program no assertion should ever fail. The next few sections show some good examples of when to use assertions.

State Assertions

Some assertions are used to test that the current state is always as it should be.

public void setEnds(Point p1, Point p2) {
    this.p1 = p1;
    this.p2 = p2;
    distance = calculateDistance(p1, p2);
    assert (distance >= 0) : "Negative distance";
}

In the above code, setting the endpoints causes the distance field to be recalculated. The distance calculation should always result in a positive value. Any bug that gives distance a negative value would produce odd effects when the value is used in the future. The assertion shown will catch such a bug right at the source. Simple assertions of this form can be used in many places to ensure the sanity of your state and algorithms.

The terms precondition and postcondition refer to particular kinds of state assertion. A precondition is something that must be true before a particular chunk of code (usually a method) is executed; a postcondition must be true after. You can use assert to check pre- and postconditions, such as:

public boolean remove(Object value) {
    assert count >= 0;
    if (value == null)
        throw new NullPointerException("value");
    int orig = count;
    boolean foundIt = false;
    try {
        // remove element from list (if it's there)
        return foundIt;
    } finally {
        assert ((!foundIt && count == orig) ||
                count == orig - 1);
    }
}

Here the method enforces the precondition that the list size must be non-negative before the operation, and the postcondition that the list size should be reduced by one after removal of an element, unless the element was not in the list.

Note that the check for whether the element to remove is null is not an assert, but instead throws a NullPointerException. In general you should expect that users of your class will make mistakes, and so, where possible, you should always check for them and have defined what happens for when they make them, such as returning a flag value or throwing an exception. In effect an assertion is a way of checking that there are no bugs or unexpected usage in the code, not that the code that calls it is bug free—so do not use assertions to validate arguments passed to your non-private methods.

Assert statements may or may not be executed, so the compiler will not always consider them reachable. A variable that is initialized only in an assert statement will be considered potentially uninitialized because if asserts are disabled it will be uninitialized. This will be true even if all uses of the variable are in other assert statements.

Control Flow Assertions

You can also use assertions to verify that the control flow of some code is always what you want it to be:

// value must always be present
private void replace(int val, int nval) {
    for (int i = 0; i < values.length; i++) {
        if (values[i] == val) {
            values[i] = nval;
            return;
        }
    }
    assert false : "replace: can't find " + val;
}

This loop should never reach the end, because some index in the loop ought to cause a return. If this doesn't happen, the loop will unexpectedly fall out the bottom and the program will continue silently on its unexpected way. The assertfalse after the loop means that if the control flow ever gets there you will be told.

In this kind of case you can reasonably avoid the assertion mechanism entirely and go straight to throwing the error yourself, replacing the assert with a throw:

throw new AssertionError("replace: can't find " + val);

Using a throw is especially valuable for methods that return values. Without a throw the compiler would not know that the method cannot get to the bottom, so it insists that you return a value at the end. That code typically looks like this:

return -1;  // never happens

The comment “never happens” asserts something to be true but doesn't check or enforce it. Only a human reading the code knows the invariant. And an assertfalse could be turned off, so the compiler would still require the code to have a bogus return. The throw enforces what you already know.

The compiler will not let you have an assert statement it believes to be unreachable, just like it won't let you have any other unreachable statement. When the compiler can determine that a line is unreachable, not only do you need no assert, you may not put one there.

Turning Assertions On and Off

By default, assertion evaluation is turned off. You can turn on all assertion evaluation in a virtual machine, or on a package, class, or class loader basis. Because they can be on or off, you must be careful to avoid side effects that affect non-assertion code.

You control assertion evaluation either by passing command-line options to the virtual machine—described shortly—or by using methods provided by the class loader—see “Controlling Assertions at Runtime” on page 444.

Why Turn Assertions On and Off?

The first question to answer is why you would want to be able to switch assertion evaluation on and off. The usual example is to turn assertions on during development, but turn them off when your system is shipping. The argument is that you will have caught any insanities during the development cycle so the overhead of doing all the checks is not worth the effort.

This is tempting logic, but you can open yourself up to serious problems. The point of assertions is to catch bugs early before they corrupt things and cause bizarre side effects. When someone else is running your code, tracking down causes for problems is much more difficult, so catching them early and reporting them clearly is much more important, even if it should happen a lot less often. You should only turn off assertions in code where they have very noticeable performance effects. In those critical sections of code, though, you will want to turn them off.

Controlling Assertions on the Command Line

Assertion evaluation is off by default. To change this, you can use standard command line options if you are using a command-line-driven virtual machine:

  • -enableassertions/-ea[descriptor]

    • Enables (turns on) assertion evaluation as defined by the descriptor. If there is no descriptor, assertions are enabled for all classes except those loaded by the system class loader—see “Loading Classes” on page 435.

  • -disableassertions/-da[descriptor]

    • Disables (turns off) assertion evaluation for all classes as defined by the descriptor. If there is no descriptor, assertions are disabled for all classes.

The descriptor allows you to specify particular packages or classes that will be affected by the option. An absent descriptor means the option applies to all non-system classes (that is, classes loaded by any class loader but the system class loader). Packages are defined by name, followed by ... which means to apply the option to all subpackages. For example, the option

 -enableassertions:com.acme...

would turn on assertions in all classes in com.acme and all its subpackages. If the descriptor consists only of ... it represents the unnamed package in the current working directory. Without the ... the descriptor is assumed to be the full name of a class.

 -enableassertions:com.acme.Plotter

turns on assertions for the class com.acme.Plotter. So if you want to turn on all assertions in com.acme, but the class com.acme.Evalutor is not relevant, you could say

 -enableassertions:com.acme... -da:com.acme.Evaluator

This applies the option to the Evaluator class and any of its nested types.

If you are using assertions in your code, the obvious thing you should do is always use –ea with enough specificity to get all your relevant code.

Multiple options are evaluated in order from the command line. Class-specific options take precedence over package ones, and a more specific package option takes precedence over a less specific one. For example, given

 -da:com.acme.Plotter -ea:com.acme... -da:com.acme.products
 -ea:com.acme.products.Rocket

assertions will be enabled for all classes and subpackages of com.acme, except the class com.acme.Plotter, and disabled for all classes and subpackages of com.acme.products, except for the class com.acme.products.Rocket.

The system classes are controlled separately because you are more likely to be searching for bugs in your code than in the system code. To affect system classes, use the “system” variants of the options: -enablesystemassertions/-esa and -disablesystemassertions/-dsa (the latter option is primarily for symmetry).

The assertion status of a class is determined when that class is initialized—see “Preparing a Class for Use” on page 441—and never changes. This determination is made before any static initializers for the class are executed, but after the initialization of the superclass. If a superclass static initializer causes a static method of its own subclass to be executed, it is possible for that static method to execute before its own class is initialized, and before the assertion status of that class has been determined. In such circumstances, the execution of any assert statements must be dealt with as if assertions were enabled for the class.

Complete Removal

Even though assertions may never get executed at runtime, the code associated with them still exists, and they can still consume virtual machine resources and add overhead to the performance of an application. For most applications this isn't something you should worry about—and a good just-in-time compiler will take care of the overhead—but sometimes, in resource constrained environments, you do have to worry about it. The only way to guarantee the complete removal of assertion related code is to edit the code and remove the assertions. But there is a standard Java programming idiom that gives the source code compiler a hint that you don't want certain sections of code present under some circumstances:

private static final boolean doAssert = true;

    if (doAssert)
        assert (cleared || size == origSize);

In the above code fragment, asserts will be checked only if the doAssert variable is true. The doAssert variable is a compile-time constant—it is static, final, and initialized with a constant value—and references to compile-time constants are replaced, at compile time, with the value of that constant. If doAssert is false nothing will ever cause the assert to get executed, so the if statement can be completely removed by the compiler, if it chooses. You can also use this technique to elide debugging print statements, for example.

Making Assertions Required

Sometimes (rarely) you may need to ensure that some class never has its assertions turned off. You can do this with code like the following

static {
    boolean assertsEnabled = false;
    assert assertsEnabled = true;
    if (!assertsEnabled)
        throw new IllegalStateException("Asserts required");
}

In this code we purposefully use a side effect of the assert statement so that the code can determine whether asserts have been turned off—if they have then the class will not load. This is a nearly unique case in which side effects are a good thing.

Requiring assertions to be on complicates the running environment of the code since it must always turn on assertions for this class even if they are off elsewhere. You should do this almost, if not actually, never.

 

The greatest of all faults is to be conscious of none.

 
 --Thomas Carlyle
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.249.198