Tips
"Effective Java, third Edition" an English version has been published, the second edition of this book presumably many people have read, known as one of the four major Java books, but the second edition of 2009 published, to now nearly 8 years, but with Java 6, 7, 8, and even 9 of the release, the Java language has undergone profound changes.
In the first time here translated into Chinese version. For everyone to learn to share.
31. Use qualified wildcard characters to increase the flexibility of the API
As described in entry 28, the parameterized type is invariant. In other words, for any two different types of Type1
and Type
, List <Type1>
neither List <Type2>
subtype nor its parent type. Although a List <String>
non List <Object>
-subtype is counterintuitive, it does make sense. You can put any object List <Object>
in, but only put the string List <String>
in. Because List <String>
you can't do List <Object>
all the things you can do, it's not a subtype (the Richter substitution principle in Item 10).
In contrast to the immutable types provided, sometimes you need more flexibility than this. Consider the class in entry 29 Stack
. The following is its public API:
public class Stack<E> { public Stack(); public void push(E e); public E pop(); public boolean isEmpty();}
Suppose we want to add a method to get a series of elements and push them all onto the stack. The following is the first attempt:
// pushAll method without wildcard type - deficient!public void pushAll(Iterable<E> src) { for (E e : src) push(e);}
This method can be cleanly compiled, but not entirely satisfactory. If the type of element that can be traversed src
exactly matches the element type of the stack, it works fine. However, suppose there Stack <Number>
is one, and push(intVal)
the invocation, where intVal
the type is Integer
. This is because it Integer
is a Number
subtype. Logically, this also seems to work:
Stack<Number> numberStack = new Stack<>();Iterable<Integer> integers = ... ;numberStack.pushAll(integers);
However, if you try, you get this error message because the parameterized type is constant:
StackTest.java:7: error: incompatible types: Iterable<Integer>cannot be converted to Iterable<Number> numberStack.pushAll(integers); ^
Fortunately, there is a corresponding workaround. The language provides a special parameterized type to invoke a qualified wildcard type to handle this situation. pushAll
the type of the input parameter should not be "E's iterable interface", but should be "the Iterable interface of a subtype of E", and there is a wildcard type, which means: Iterable <? extends E>
. ( extends
the use of keywords is a bit misleading: Recall entry 29, subtypes are defined as each type is its own subtype, even if it does not inherit itself.) Let's modify it pushAll
to use this type:
// Wildcard type for a parameter that serves as an E producerpublic void pushAll(Iterable<? extends E> src) { for (E e : src) push(e);}
With this change, the Stack
class can not only compile cleanly, but also the client code will not be compiled with the original pushAll
declaration. Because Stack
and its client is cleanly compiled, you know that everything is type-safe.
Now suppose you want to write a popAll
method that pushAll
corresponds to a square. popAll
method pops each element from the stack and adds the element to the given collection. The following is the first attempt to write popAll
a method:
// popAll method without wildcard type - deficient!public void popAll(Collection<E> dst) { while (!isEmpty()) dst.add(pop());}
Similarly, if the element type of the target collection exactly matches the element type of the stack, it is cleanly compiled and works correctly. However, this is not entirely satisfactory. Suppose you have a Stack <Number>
Object
variable of type. If an element is popped from the stack and stored in the variable, it will compile and run without error. So you can't do that, too?
Stack<Number> numberStack = new Stack<Number>();Collection<Object> objects = ... ;numberStack.popAll(objects);
If you try to compile this client code with the popAll
version that was previously displayed, you get an error that is very similar to our first version pushAll
: It Collection <Object>
is not Collection <Number>
a subtype. The wildcard type once again provides a way out. popAll
the type of the input parameter should not be "collection of e", but should be "a collection of one of the parent types of E" (where the parent type is defined as E is its own parent type [jls,4.10]). Again, there is a wildcard type, exactly this meaning: Collection <? super E>
. Let's modify popAll
it to use it:
// Wildcard type for parameter that serves as an E consumerpublic void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop());}
With this change, both the stack class and the client code can be cleanly compiled.
The conclusion is clear. For maximum flexibility, use a wildcard type for input parameters that represent the producer or consumer . If an input parameter is both a producer and a consumer, then the wildcard type is not good for you: you need an exact type match, which is the case without any wildcard characters.
Here is a mnemonic to help you remember which wildcard type to use:
PECS representative: Producer-extends,consumer-super.
In other words, if a parameterized type represents a T
producer, <? extends T>
it is used if it represents a T
consumer <? super T>
. In our Stack
example, the pushAll
parameters of the method are src
generated using an instance of the stack E
, so src
that the appropriate type is the Iterable<? extends E>
popAll
method of dst
argument consumption Stack
in the E
instance, so ds
The appropriate type of T is Collection <? super E>
. The Pecs mnemonic captures the basic principles of using a wildcard character type. Naftalin and Wadler call the acquisition and placement principle (get and put Principle) [naftalin07,2.4].
After remembering this mnemonic, let's take a look at some of the methods and construction method declarations of previous projects in this chapter. The Chooser
class construction method in entry 28 has such a declaration:
public Chooser(Collection<T> choices)
This construction method uses only the collection selection to produce T
the values of the type (and stores them for later use), so its declaration should use a extends T
wildcard type. The following is the resulting construction method declaration:
// Wildcard type for parameter that serves as an T producerpublic Chooser(Collection<? extends T> choices)
Will this change be different in practice? Yes, it's going to be different. False you have a List <Integer>
, and want to pass it to Chooser<Number>
the construction method. This is not compiled with the original declaration, but it only adds qualified wildcard types to the declaration.
Now look at the method in entry 30 union
. The following is the statement:
public static <E> Set<E> union(Set<E> s1, Set<E> s2)
Two parameters s1
and s2
All are E
producers, so the Pecs mnemonic tells us that the declaration should look like this:
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)
Please note that the return type is still Set <E>
. Do not use a qualified wildcard type as the return type. In addition to providing users with additional flexibility, they are also forced to use wildcard types in client code. With the modified declaration, this code will be compiled clearly:
Set<Integer> integers = Set.of(1, 3, 5);Set<Double> doubles = Set.of(2.0, 4.0, 6.0);Set<Number> numbers = union(integers, doubles);
If used properly, the user of the class will almost never see a wildcard type. They make the method accept the arguments they should accept and reject the arguments that they should reject. If a user of a class must consider a wildcard type, then its API may be problematic.
Prior to Java 8, the type inference rule was not smart enough to handle the previous code fragment, which required the compiler to infer the type using the return type (or target type) specified by the context E
. union
the target type of the method invocation is as shown earlier Set <Number>
. If you try to compile a fragment in an earlier version of Java (and a suitable Set.of
factory replacement version), you will see such a lengthy and intricate error message:
Union.java:14: error: incompatible types Set<Number> numbers = union(integers, doubles); ^ required: Set<Number> found: Set<INT#1> where INT#1,INT#2 are intersection types: INT#1 extends Number,Comparable<? extends INT#2> INT#2 extends Number,Comparable<?>
Fortunately there is a way to deal with this kind of mistake. If the compiler cannot infer the correct type, you can always tell it what type of explicit type parameter to use [jls,15.12]. Even before introducing the target type into Java 8, this is not something you have to do frequently, which is good because the explicit type parameters are not very beautiful. By adding an explicit type parameter, as shown below, the code snippet was cleanly compiled in versions prior to Java 8:
// Explicit type parameter - required prior to Java 8Set<Number> numbers = Union.<Number>union(integers, doubles);
Now let's turn our attention to the method in article 30 max
. Here is the original statement:
public static <T extends Comparable<T>> T max(List<T> list)
The following is a modified declaration that uses a wildcard type:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
We applied the pecs two times in order to change from the original to the revised statement. The first Direct application is the parameter list. It generates T
an instance, so change the type from to List <T>
List<? extends T>
. The tricky application is the type parameter T
. This is the first time we see a wildcard applied to a type parameter. Initially, T
the consumption instance is specified as inherited Comparable <T>
but Comparable
T
T
(and generates an integer indicating the order relationship). Therefore, the parameterized type Comparable <T>
is replaced with a qualified wildcard type Comparable<? super T>
. Comparable
instances are always consumers, so it should usually be better to use Comparable<? super T>
than Comparable <T>
. The same Comparator
is true. Therefore, it should usually be better to use Comparator<? super T>
than Comparator<T>
.
The revised max
statement may be the most complex method statement in the book. Does the added complexity really work? Again, it does. This is a simple example of a list that is excluded from the original declaration, but is allowed in the modified version:
List<ScheduledFuture<?>> scheduledFutures = ... ;
The reason why the original method declaration could not be applied to this list is ScheduledFuture
not implemented Comparable <ScheduledFuture>
. Instead, it is a Delayed
sub-interface, which inherits Comparable <Delayed>
. In other words, an ScheduledFuture
instance is not only compared to other ScheduledFuture
instances: it can be compared with any instance and is Delayed
sufficient to cause the original claim to reject it. More generally, wildcards are required to support types that do not have a direct implementation Comparable
(or Comparator
), but inherit a type.
There is also a topic related to wildcard characters. There is a duality between a type parameter and a wildcard, and many methods can be declared with one or the other. For example, here are two possible declarations that are used to exchange static methods for two indexed items in a list. The first one uses the unrestricted type parameter (entry 30), and the second uses the unrestricted wildcard character:
// Two possible declarations for the swap methodpublic static <E> void swap(List<E> list, int i, int j);public static void swap(List<?> list, int i, int j);
Which of these two statements is preferable, why? In the public API, the second one is better because it's simpler. You pass in a list (any list), and the method swaps the elements of the index. There are no type parameters to worry about. In general, if a type parameter appears only once in a method declaration, replace it with a wildcard character . If it is an unrestricted type parameter, replace it with an unrestricted wildcard character; If it is a qualified type parameter, replace it with qualified wildcard characters.
The second swap
method declares that there is a problem. This simple implementation does not compile:
public static void swap(List<?> list, int i, int j) { list.set(i, list.set(j, list.get(i)));}
Trying to compile it will produce this less useful error message:
Swap.java:5: error: incompatible types: Object cannot beconverted to CAP#1 list.set(i, list.set(j, list.get(i))); ^ where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ?
It seems that we can't put an element back in the list we just took out. The problem is that the type of the list is List <?>
, and you cannot put any values except null List <?>
in. Fortunately, there is a way to implement this method without using unsafe transformations or primitive types. The idea is to write a private helper method to catch a wildcard type. The helper method must be a generic method to capture the type. Here's how it's defined:
public static void swap(List<?> list, int i, int j) { swapHelper(list, i, j);}// Private helper method for wildcard captureprivate static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i)));}
swapHelper
Method knows that the list is one List <E>
. Therefore, it knows that any value obtained from this list is of type E and can safely place values of any type in the E
list. This slightly more complex swap
implementation can be compiled cleanly. It allows us to export pretty declarations based on wildcards while taking advantage of the more complex generic methods inside. swap
the client of the method does not need to face more complex swapHelper
claims, but they benefit from it. The helper method has signatures that we think are overly complex for public methods.
In summary, using wildcard types in your API is tricky, but makes the API more flexible. If you write a class library that will be widely used, the proper use of wildcard types should be considered mandatory. Remember the basic rules: Producer-extends, Consumer-super (PECS). Also remember that all Comparable
and all Comparator
are consumers.
Effective Java version--31. Using qualified wildcard characters to increase the flexibility of the API