Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Effective Java Programming Language Guide - Bloch J..pdf
Скачиваний:
41
Добавлен:
24.05.2014
Размер:
2.93 Mб
Скачать

Effective Java: Programming Language Guide

resulting API is more flexible. In cases where the programmer knows the call will succeed or is content to let the thread terminate if the call fails, the transformation also allows this simple calling sequence:

obj.action(args);

If you suspect that the simple calling sequence will be the norm, then this API transformation may be appropriate. The API resulting from this transformation is essentially identical to the “state-testing method” API in Item 39 and the same caveats apply: If an object is to be accessed concurrently without external synchronization or it is subject to externally induced state transitions, this transformation is inappropriate, as the object's state may change between the invocations of actionPermitted and action. If a separate actionPermitted method would, of necessity, duplicate the work of the action method, the transformation may be ruled out by performance concerns.

Item 42:Favor the use of standard exceptions

One of the attributes that most strongly distinguishes expert programmers from less experienced ones is that experts strive for and usually achieve a high degree of code reuse. Exceptions are no exception to the general rule that code reuse is good. The Java platform libraries provide a basic set of unchecked exceptions that cover a large fraction of the exception-throwing needs of most APIs. In this item, we'll discuss these commonly reused exceptions.

Reusing preexisting exceptions has several benefits. Chief among these, it makes your API easier to learn and use because it matches established conventions with which programmers are already familiar. A close second is that programs using your API are easier to read because they aren't cluttered with unfamiliar exceptions. Finally, fewer exception classes mean a smaller memory footprint and less time spent loading classes.

The most commonly reused exception is IllegalArgumentException. This is generally the exception to throw when the caller passes in an argument whose value is inappropriate. For example, this would be the exception to throw if the caller passed a negative number in a parameter representing the number of times some action were to be repeated.

Another commonly reused exception is IllegalStateException. This is generally the exception to throw if the invocation is illegal, given the state of the receiving object. For example, this would be the exception to throw if the caller attempted to use some object before it had been properly initialized.

Arguably, all erroneous method invocations boil down to an illegal argument or illegal state, but other exceptions are standardly used for certain kinds of illegal arguments and states. If a caller passes null in some parameter for which null values are prohibited, convention dictates that NullPointerException be thrown rather than IllegalArgumentException. Similarly, if a caller passes an out-of-range value in a parameter representing an index into a sequence, IndexOutOfBoundsException should be thrown rather than

IllegalArgumentException.

132

Effective Java: Programming Language Guide

Another general-purpose exception worth knowing about is ConcurrentModificationException. This exception should be thrown if an object designed for use by a single thread or with external synchronization detects that it is being (or has been) concurrently modified.

A last general-purpose exception worthy of note is UnsupportedOperationException. This is the exception to throw if an object does not support an attempted operation. Its use is rare compared to that of other exceptions discussed in this item, as most objects support all the methods they implement. This exception is used by implementations of interfaces that fail to implement one or more optional operations defined by the interface. For example, an appendonly List implementation would throw this exception if someone tried to delete an element.

Table 8.1 summarizes the most commonly reused exceptions.

Table 8.1. Commonly Used Exceptions

Exception

Occasion for Use

IllegalArgumentException

Parameter value is inappropriate

IllegalStateException

Object state is inappropriate for method invocation

NullPointerException

Parameter value is null where prohibited

IndexOutOfBoundsException

Index parameter value is out of range

ConcurrentModificationException

Concurrent modification of object has been detected where

 

prohibited

UnsupportedOperationException

Object does not support method

While these are by far are the most commonly reused exceptions in the Java platform libraries, other exceptions may be reused where circumstances warrant. For example, it would be appropriate to reuse ArithmeticException and NumberFormatException if you were implementing arithmetic objects like complex numbers or matrices. If an exception fits your needs, go ahead and use it, but only if the conditions under which you would throw it are consistent with the exception's documentation. Reuse must be based on semantics, not just on name. Also, feel free to subclass an existing exception if you want to add a bit more failure-capture information (Item 45).

Finally, be aware that choosing which exception to reuse is not always an exact science, as the “occasions for use” in the Table 8.1 are not mutually exclusive. Consider, for example, the case of an object representing a deck of cards. Suppose there were a method to deal a hand from the deck that took as an argument the size of the hand. Suppose the caller passed in this parameter a value that was larger than the number of cards remaining in the deck. This could be construed as an IllegalArgumentException (the handSize parameter value is too high) or an IllegalStateException (the deck object contains too few cards for the request). In this case the IllegalArgumentException feels right, but there are no hard-and-fast rules.

Item 43: Throw exceptions appropriate to the abstraction

It is disconcerting when a method throws an exception that has no apparent connection to the task that it performs. This often happens when a method propagates an exception thrown by a lower-level abstraction. Besides being disconcerting, this pollutes the API of the higher layer with implementation details. If the implementation of the higher layer is changed in a subsequent release, the exceptions that it throws may change as well, potentially breaking existing client programs.

133

Effective Java: Programming Language Guide

To avoid this problem, higher layers should catch lower-level exceptions and, in their place, throw exceptions that are explainable in terms of the higher-level abstraction.

This idiom, which we call exception translation, looks like this:

// Exception Translation try {

// Use lower-level abstraction to do our bidding

...

} catch(LowerLevelException e) {

throw new HigherLevelException(...);

}

Here is an example of exception transaction taken from the AbstractSequentialList class, which is a skeletal implementation (Item 16) of the List interface. In this example, exception translation is mandated by the specification of the get method in the List interface:

/**

*Returns the element at the specified position in this list.

*@throws IndexOutOfBoundsException if the index is out of range

*(index < 0 || index >= size()).

*/

public Object get(int index) { ListIterator i = listIterator(index); try {

return i.next();

} catch(NoSuchElementException e) {

throw new IndexOutOfBoundsException("Index: " + index);

}

}

A special form of exception translation called exception chaining is appropriate in cases where the lower-level exception might be helpful to someone debugging the situation that caused the exception. In this approach, the lower-level exception is stored by the higher-level exception, which provides a public accessor method to retrieve the lower-level exception:

// Exception Chaining try {

// Use lower-level abstraction to do our bidding

...

} catch (LowerLevelException e) { throw new HigherLevelException(e);

}

As of release 1.4, exception chaining is supported by Throwable. If you're targeting release 1.4 (or a later one), you can take advantage of this support by having your higher-level exception's constructor chain to Throwable(Throwable):

// Exception chaining in release 1.4

HigherLevelException(Throwable t) { super(t);

}

134

Effective Java: Programming Language Guide

If you're targeting an earlier release, your exception must store the lower-level exception and provide an accessor:

// Exception chaining prior to release 1.4 private Throwable cause;

HigherLevelException(Throwable t) { cause = t;

}

public Throwable getCause() { return cause;

}

By naming the accessor getCause and using the shown declaration, you ensure that your exception will interoperate with the platform's chaining facility should you use the exception in a release like 1.4. This has the advantage of integrating the lower-level exception's stack trace into that of the higher-level exception in a standard fashion. Also, it allows standard debugging tools to access the lower-level exception.

While exception translation is superior to mindless propagation of exceptions from lower layers, it should not be overused. Where possible, the best way to deal with exceptions from lower layers is to avoid them entirely by ensuring that lower-level methods will succeed before invoking them. Sometimes you can do this by explicitly checking the validity of the higher-level method's arguments before passing them on to lower layers.

If it is impossible to prevent exceptions from lower layers, the next best thing is to have the higher layer silently work around these exceptions, insulating the caller of the higher-level method from the lower-level problem. Under these circumstances, it may be appropriate to log the exception using some appropriate logging facility such as java.util.logging, which was introduced in release 1.4. This allows an administrator to investigate the problem, while insulating the client code and the end user from it.

In situations where it is not feasible to prevent exceptions from lower layers or to insulate higher layers from them, exception translation should generally be used. Only if the lowerlevel method's specification happens to guarantee that all of the exceptions it throws are appropriate to the higher level should exceptions be allowed to propagate from the lower layer to the higher.

Item 44:Document all exceptions thrown by each method

A description of the exceptions thrown by a method comprises an important part of the documentation required to use the method properly. Therefore it is critically important that you take the time to carefully document all of the exceptions thrown by each method.

Always declare checked exceptions individually, and document precisely the conditions under which each one is thrown using the Javadoc @throws tag. Don't take the shortcut of declaring that a method throws some superclass of multiple exception classes that it may throw. As an extreme example, never declare that a method “throws Exception” or, worse yet, “throws Throwable.” In addition to denying any guidance to the programmer concerning the exceptions that the method is capable of throwing, such a declaration greatly hinders the

135