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

Effective Java: Programming Language Guide

In summary, the advantages of typesafe enums over int enums are great, and none of the disadvantages seem compelling unless an enumerated type is to be used primarily as a set element or in a severely resource constrained environment. Thus the typesafe enum pattern should be what comes to mind when circumstances call for an enumerated type. APIs that use typesafe enums are far more programmer friendly than those that use int enums. The only reason that typesafe enums are not used more heavily in the Java platform APIs is that the typesafe enum pattern was unknown when many of those APIs were written. Finally, it's worth reiterating that the need for enumerated types of any sort should be relatively rare, as a major use of these types has been made obsolete by subclassing (Item 20).

Item 22: Replace function pointers with classes and interfaces

C supports function pointers, which allow a program to store and transmit the ability to invoke a particular function. Function pointers are typically used to allow the caller of a function to specialize its behavior by passing in a pointer to a second function, sometimes

referred

to

as

a callback. For example, the qsort function in C's standard library takes

a pointer

to

a

comparator function, which it uses to compare the elements to be sorted.

The comparator function takes two parameters, each of which is a pointer to an element. It returns a negative integer if the element pointed to by the first parameter is less than the one pointed to by the second, zero if the two elements are equal, and a positive integer if the element pointed to by the first parameter is greater than the one pointed to by the second. Different sort orders can be obtained by passing in different comparator functions. This is an example of the Strategy pattern [Gamma98, p.315]; the comparator function represents a strategy for sorting elements.

Function pointers were omitted from the Java programming language because object references can be used to provide the same functionality. Invoking a method on an object typically performs some operation on that object. However, it is possible to define an object whose methods perform operations on other objects, passed explicitly to the methods. An instance of a class that exports exactly one such method is effectively a pointer to that method. Such instances are known as function objects. For example, consider the following class:

class StringLengthComparator {

public int compare(String s1, String s2) { return s1.length() - s2.length();

}

}

This class exports a single method that takes two strings and returns a negative integer if the first string is shorter than the second, zero if the two strings are of equal length, and a positive integer if the first string is longer. This method is a comparator that orders strings

based on

their

length

instead of the more

typical lexicographic ordering. A

reference to

a StringLengthComparator object serves

as

a “function

pointer”

to

this

comparator,

allowing

it

to be

invoked on arbitrary

pairs of

strings.

In

other words,

a StringLengthComparator instance is a concrete strategy for string comparison.

As is typical for concrete strategy classes, the StringLengthComparator class is stateless: It has no fields, hence all instances of the class are functionally equivalent to one another. Thus

88

Effective Java: Programming Language Guide

it could just as well be a singleton to save on unnecessary object creation costs (Item 4, Item 2):

class StringLengthComparator {

private StringLengthComparator() { }

public static final StringLengthComparator INSTANCE = new StringLengthComparator();

public int compare(String s1, String s2) { return s1.length() - s2.length();

}

}

To pass a StringLengthComparator instance to a method, we need an appropriate type for the parameter. It would do no good to use StringLengthComparator because clients would be unable to pass any other comparison strategy. Instead, we need to define a Comparator interface and modify StringLengthComparator to implement this interface. In other words, we need to define a strategy interface to go with the concrete strategy class. Here it is:

// Strategy interface

public interface Comparator {

public int compare(Object o1, Object o2);

}

This definition of the Comparator interface happens to come from the java.util package, but there's nothing magic about it; you could just as well have defined it yourself. So that it is applicable to comparators for objects other than strings, its compare method takes parameters of type Object rather than String. Therefore, the StringLengthComparator class shown earlier must be modified slightly to implement Comparator: The Object parameters must be cast to String prior to invoking the length method.

Concrete strategy classes are often declared using anonymous classes (Item 18). The following statement sorts an array of strings according to length:

Arrays.sort(stringArray, new Comparator() { public int compare(Object o1, Object o2) {

String s1 = (String)o1;

String s2 = (String)o2;

return s1.length() - s2.length();

}

});

Because the strategy interface serves as a type for all of its concrete strategy instances, a concrete strategy class needn't be made public to export a concrete strategy. Instead, a “host class” can export a public static field (or static factory method) whose type is the strategy interface, and the concrete strategy class can be a private nested class of the host. In the example that follows, a static member class is used in preference to an anonymous class to allow the concrete strategy class to implement a second interface, Serializable:

89

CASE_INSENSITIVE_ORDER

Effective Java: Programming Language Guide

// Exporting a concrete strategy class Host {

... // Bulk of class omitted

private static class StrLenCmp

implements Comparator, Serializable { public int compare(Object o1, Object o2) {

String s1 = (String)o1;

String s2 = (String)o2;

return s1.length() - s2.length();

}

}

// Returned comparator is serializable public static final Comparator

STRING_LENGTH_COMPARATOR = new StrLenCmp();

}

The String class uses this pattern to export a case-independent string comparator via its field.

To summarize, the primary use of C's function pointers is to implement the Strategy pattern. To implement this pattern in the Java programming language, declare an interface to represent the strategy and a class that implements this interface for each concrete strategy. When a concrete strategy is used only once, its class is typically declared and instantiated using an anonymous class. When a concrete strategy is exported for repeated use, its class is generally a private static member class, and it is exported via a public static final field whose type is the strategy interface.

90

Effective Java: Programming Language Guide

Chapter 6. Methods

This chapter discusses several aspects of method design: how to treat parameters and return values, how to design method signatures, and how to document methods. Much of the material in this chapter applies to constructors as well as to methods. Like Chapter 5, this chapter focuses on usability, robustness, and flexibility.

Item 23: Check parameters for validity

Most methods and constructors have some restrictions on what values may be passed into their parameters. For example, it is not uncommon that index values must be nonnegative and object references must be non-null. You should clearly document all such restrictions and enforce them with checks at the beginning of the method body. This is a special case of the general principle, and you should attempt to detect errors as soon as possible after they occur. Failing to do so makes it less likely that an error will be detected and makes it harder to determine the source of an error once it has been detected.

If an invalid parameter value is passed to a method and the method checks its parameters before execution, it will fail quickly and cleanly with an appropriate exception. If the method fails to check its parameters, several things could happen. The method could fail with a confusing exception in the midst of processing. Worse, the method could return normally but silently compute the wrong result. Worst of all, the method could return normally but leave some object in a compromised state, causing an error at some unrelated point in the code at some undetermined time in the future.

For public methods, use the Javadoc @throws tag to document the exception that will be thrown if a restriction on parameter values is violated (Item 44). Typically the exception will

be IllegalArgumentException, IndexOutOfBoundsException, or NullPointerException

(Item 42). Once you've documented the restrictions on a method's parameters and you've documented the exceptions that will be thrown if these restrictions are violated, it is a simple matter to enforce the restrictions. Here's a typical example:

/**

*Returns a BigInteger whose value is (this mod m). This method

*differs from the remainder method in that it always returns a

*nonnegative BigInteger.

*

* @param m the modulus, which must be positive.

*@return this mod m.

*@throws ArithmeticException if m <= 0.

*/

public BigInteger mod(BigInteger m) { if (m.signum() <= 0)

throw new ArithmeticException("Modulus not positive");

... // Do the computation

}

For an unexported method, you as the package author control the circumstances under which the method is called, so you can and should ensure that only valid parameter values are ever passed in. Therefore nonpublic methods should generally check their parameters using

91

Effective Java: Programming Language Guide

assertions rather than normal checks. If you are using a release of the platform that supports assertions (1.4 or later), you should use the assert construct; otherwise you should use a makeshift assertion mechanism.

It is particularly important to check the validity of parameters that are not used by a method but are stored away for later use. For example, consider the static factory method on page 86, which takes an int array and returns a List view of the array. If a client of this method were to pass in null, the method would throw a NullPointerException because the method contains an explicit check. If the check had been omitted, the method would return a reference to a newly created List instance that would throw a NullPointerException as soon as a client attempted to use it. By that time, unfortunately, the origin of the List instance might be very difficult to determine, which could greatly complicate the task of debugging.

Constructors represent a special case of the principle that you should check the validity of parameters that are to be stored away for later use. It is very important to check the validity of parameters to constructors to prevent the construction of an object that violates class invariants.

There are exceptions to the rule that you should check a method's parameters before performing its computation. An important exception is the case in which the validity check would be expensive or impractical and the validity check is performed implicitly in the process of doing the computation. For example, consider a method that sorts a list of objects, such as Collections.sort(List). All of the objects in the list must be mutually comparable. In the process of sorting the list, every object in the list will be compared to some other object in the list. If the objects aren't mutually comparable, one of these comparisons will throw a ClassCastException, which is exactly what the sort method should do. Therefore there would be little point in checking ahead of time that the elements in the list were mutually comparable. Note, however, that indiscriminate application of this technique can result in a loss of failure atomicity (Item 46).

Occasionally, a computation implicitly performs the required validity check on some parameter but throws the wrong exception if the check fails. That is to say, the exception that the computation would naturally throw as the result of an invalid parameter value does not match the exception that you have documented the method to throw. Under these circumstances, you should use the exception translation idiom described in Item 43 to translate the natural exception into the correct one.

Do not infer from this item that arbitrary restrictions on parameters are a good thing. On the contrary, you should design methods to be as general as it is practical to make them. The fewer restrictions that you place on parameters, the better, assuming the method can do something reasonable with all of the parameter values that it accepts. Often, however, some restrictions are intrinsic to the abstraction being implemented.

To summarize, each time you write a method or constructor, you should think about what restrictions exist on its parameters. You should document these restrictions and enforce them with explicit checks at the beginning of the method body. It is important to get into the habit of doing this; the modest work that it entails will be paid back with interest the first time a validity check fails.

92