Representing Exceptions in Source Code

An exception can be represented via error codes or objects. This section discusses each kind of representation and explains why objects are superior. It then introduces you to Java's exception and error class hierarchy, emphasizing the difference between checked and runtime exceptions. It closes by discussing custom exception classes.

Error Codes Versus Objects

One way to represent exceptions in source code is to use error codes. For example, a method might return true on success and false when an exception occurs. Alternatively, a method might return 0 on success and a nonzero integer value that identifies a specific kind of exception.

Developers traditionally designed methods to return error codes; I demonstrated this tradition in each of the three methods in Listing 4-16's Logger interface. Each method returns true on success, or returns false to represent an exception (unable to connect to the logger, for example).

Although a method's return value must be examined to see if it represents an exception, error codes are all too easy to ignore. For example, a lazy developer might ignore the return code from Logger's connect() method and attempt to call log(). Ignoring error codes is one reason why a new approach to dealing with exceptions has been invented.

This new approach is based on objects. When an exception occurs, an object representing the exception is created by the code that was running when the exception occurred. Details describing the exception's surrounding context are stored in the object. These details are later examined to work around the exception.

The object is then thrown, or handed off to the virtual machine to search for a handler, code that can handle the exception. (If the exception is an error, the application should not provide a handler.) When a handler is located, its code is executed to provide a workaround. Otherwise, the virtual machine terminates the application.

Apart from being too easy to ignore, an error code's Boolean or integer value is less meaningful than an object name. For example, fileNotFound is self-evident, but what does false mean? Also, an object can contain information about what led to the exception. These details can be helpful to a suitable workaround.

The Throwable Class Hierarchy

Java provides a hierarchy of classes that represent different kinds of exceptions. These classes are rooted in java.lang.Throwable, the ultimate superclass for all throwables (exception and error objects—exceptions and errors, for short—that can be thrown). Table 4-1 identifies and describes most of Throwable's constructors and methods.

Table 4-1. Throwable's Constructors and Methods

Method

Description

Throwable()

Throwable(String message)

Create a throwable with a null detail message and cause.

Create a throwable with the specified detail message and a null cause.

Throwable(String message, Throwable cause)

Create a throwable with the specified detail message and cause.

Throwable(Throwable cause)

Create a throwable whose detail message is the string representation of a nonnull cause, or null.

Throwable getCause()

Return the cause of this throwable. If there is no cause, null is returned.

String getMessage()

StackTraceElement[] getStackTrace()

Return this throwable's detail message, which might be null.

Provide programmatic access to the stack trace information printed by printStackTrace() as an array of stack trace elements, each representing one stack frame.

Method Description

Throwable initCause(Throwable Initialize the cause of this throwable to the specified value. cause)

Method Description

Throwable initCause(Throwable Initialize the cause of this throwable to the specified value. cause)

void printStackTrace() Print this throwable and its backtrace of stack frames to the standard error stream.

It is not uncommon for a class's public methods to call helper methods that throw various exceptions. A public method will probably not document exceptions thrown from a helper method because they are implementation details that often should not be visible to the public method's caller.

However, because this exception might be helpful in diagnosing the problem, the public method can wrap the lower-level exception in a higher-level exception that is documented in the public method's contract interface. The wrapped exception is known as a cause because its existence causes the higher-level exception to be thrown.

When an exception is thrown, it leaves behind a stack of unfinished method calls. Each stack entry is represented by an instance of the java.lang.StackTraceElement class. This class's methods provide access to information about a stack entry. For example, public String getMethodName() returns the name of an unfinished method.

Moving down the throwable hierarchy, you encounter the java.lang.Exception and java.lang.Error classes, which respectively represent exceptions and errors. Each class offers four constructors that pass their arguments to their Throwable counterparts, but provides no methods apart from those that are inherited from Throwable.

Exception is itself subclassed by java.lang.CloneNotSupportedException (discussed in Chapter 3), java.lang.IOException (discussed in Chapter 10), and other classes. Similarly, Error is itself subclassed by java.lang.AssertionError (discussed in Chapter 5), java.lang.OutOfMemoryError, and other classes.

CAUTION: Never instantiate Throwable, Exception, or Error. The resulting objects are meaningless because they are too generic.

Checked Exceptions Versus Runtime Exceptions

A checked exception is an exception that represents a problem with the possibility of recovery, and for which the developer must provide a workaround. The developer checks (examines) the code to ensure that the exception is handled in the method where it is thrown, or is explicitly identified as being handled elsewhere.

Exception and all subclasses except for RuntimeException (and its subclasses) describe checked exceptions. For example, the aforementioned CloneNotSupportedException and IOException classes describe checked exceptions. (CloneNotSupportedException should not be checked because there is no runtime workaround for this kind of exception.)

A runtime exception is an exception that represents a coding mistake. This kind of exception is also known as an unchecked exception because it does not need to be handled or explicitly identified—the mistake must be fixed. Because these exceptions can occur in many places, it would be burdensome to be forced to handle them.

RuntimeException and its subclasses describe unchecked exceptions. For example, java.lang.ArithmeticException describes arithmetic problems such as integer division by zero. Another example is java.lang.ArraylndexOutOfBoundsException. (In hindsight, RuntimeException should have been named UncheckedException because all exceptions occur at runtime.)

NOTE: Many developers are not happy with checked exceptions because of the work involved in having to handle them. This problem is made worse by libraries providing methods that throw checked exceptions when they should throw unchecked exceptions. As a result, many modern languages support only unchecked exceptions.

Custom Exception Classes

You can declare your own exception classes. Before doing so, ask yourself if an existing exception class in Java's class library meets your needs. If you find a suitable class, you should reuse it. (Why reinvent the wheel?) Other developers will already be familiar with the existing class, and this knowledge will make your code easier to learn.

If no existing class meets your needs, think about whether to subclass Exception or RuntimeException. In other words, will your exception class be checked or unchecked? As a rule of thumb, your class should subclass RuntimeException if you think that it will describe a coding mistake.

TIP: When you name your class, follow the convention of providing an Exception suffix. This suffix clarifies that your class describes an exception.

Suppose you are creating a Media class whose static methods perform various media-oriented utility tasks. For example, one method converts files in non-MP3 media formats to MP3 format. This method will be passed source file and destination file arguments, and will convert the source file to the format implied by the destination file's extension.

Before performing the conversion, the method needs to verify that the source file's format agrees with the format implied by its file extension. If there is no agreement, an exception must be thrown. Furthermore, this exception must store the expected and existing media formats so that a handler can identify them in a message to the user.

Because Java's class library does not provide a suitable exception class, you decide to introduce a class named InvalidMediaFormatException. Detecting an invalid media format is not the result of a coding mistake, and so you also decide to extend Exception to indicate that the exception is checked. Listing 4-23 presents this class's declaration.

Listing 4-23. Declaring a custom exception class public class InvalidMediaFormatException extends Exception {

private String expectedFormat; private String existingFormat;

public InvalidMediaFormatException(String expectedFormat,

String existingFormat)

super("Expected format: " + expectedFormat + ", Existing format: " +

existingFormat); this.expectedFormat = expectedFormat; this.existingFormat = existingFormat;

public String getExpectedFormat() {

return expectedFormat;

public String getExistingFormat() {

return existingFormat;

InvalidMediaFormatException provides a constructor that calls Exception's public Exception(String message) constructor with a detail message that includes the expected and existing formats. It is wise to capture such details in the detail message because the problem that led to the exception might be hard to reproduce.

InvalidMediaFormatException also provides getExpectedFormat() and getExistingFormat() methods that return these formats. Perhaps a handler will present this information in a message to the user. Unlike the detail message, this message might be localized, expressed in the user's language (French, German, English, and so on).

Was this article helpful?

0 0

Post a comment