[JAVA] Item 31: Use bounded wildcards to increase API flexibility

31. Boundary wildcards should be used for API flexibility

As was the case with Item28, the type parameter is invariant. So, for example, `List <String>` is not a subtype of `List <Object>`. Not everything that `List <Object> ``` can do can be done with `List ```, so it also fits Liskov's substitution principle. (Item10) Sometimes more flexibility is needed. Consider the Stack class used in Item29 as an example. The API of the Stack class is as follows.

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

Consider adding a method that takes a sequence of elements as an argument and puts them all on the Stack. The first thing to think about is:

// pushAll method without wildcard type - deficient!
public void pushAll(Iterable<E> src) {
    for (E e : src)
        push(e);
}

This method can be compiled, but it's not satisfactory. If the element of Iterable <E> src matches the element of Stack, it works fine, but for example, the following code will result in an error. The reason for the error is that the type parameter is invariant.

Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);

To deal with the error that appears here, use wildcards for type arguments.

// Wildcard type for a parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
        push(e);
}

This support makes it compileable and type-safe.

Next, consider the `` `popAll``` method, which takes a collection as an argument and transfers all the elements stored on the Stack to it. The draft is as follows.

// popAll method without wildcard type - deficient!
public void popAll(Collection<E> dst) {
    while (!isEmpty())
        dst.add(pop());
}

This works fine if element E exactly matches that of the Stack, but not if it doesn't. That is, the following code will result in a compilation error.

Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ... ;
numberStack.popAll(objects);

To deal with this, use wildcards for type arguments here as well, as shown below.

// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
    while (!isEmpty())
        dst.add(pop());
}

The lesson here is clear: ** use wildcards when the input argument acts as either a producer or a consumer **. If the input argument serves as either a producer or a consumer, you don't need to use wildcards. The usage is memorized as follows.

PECS stands for producer-extends, consumer-super.

This means that if the type variable T is a producer, then `<? Extends T>` is used, and if it is a consumer, then ``` <? Super T> `` `is used. Represents. The Stack example above is like that.

A concrete example of this PECS will be applied to the examples in this chapter. Consider the following constructor for Item28.

public Chooser(Collection<T> choices)

This argument acts as a producer, so according to PECS:

// Wildcard type for parameter that serves as an T producer
public Chooser(Collection<? extends T> choices)

Next, consider the following union method of Item30.

public static <E> Set<E> union(Set<E> s1, Set<E> s2)

Both arguments play the role of producer, so they are as follows.

public static <E> Set<E> union(Set<? extends E> s1,
                               Set<? extends E> s2)

The caveat here is that ** do not use bounding wildcards for return types **. Such a response would force users to use wildcards in their code rather than increase their flexibility. ** The reason users have to think about boundary wildcards when using classes is probably a problem with their API. ** **

Before Java8, you have to specify the type as follows.

// Explicit type parameter - required prior to Java 8
Set<Number> numbers = Union.<Number>union(integers, doubles);

Next, consider the max method of Item30.

public static <T extends Comparable<T>> T max(List<T> list)

When this is applied to PECS, it becomes as follows.

public static <T extends Comparable<? super T>> T max(
        List<? extends T> list)

Here, PECS is applied twice. ** Comparable is always a consumer, so you should choose `Comparable <? Super T>` from `Comparable <T>` **. ** Comparator is the same, so you should select `Comparator <? Super T>` from `Comparator <T>` **.

Another issue to discuss regarding wildcards is whether to use type parameters or wildcards. For example, there are two possible methods for swapping lists: using type parameters and using wildcards.

// Two possible declarations for the swap method
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);

As for which one is preferred, the method using the second wildcard is preferred because it is simpler. In general, if a ** type variable appears in only one place in a method, it should be replaced with a wildcard. ** ** The second method will result in a compile error if you write only that, as shown below.

public static void swap(List<?> list, int i, int j) {
    list.set(i, list.set(j, list.get(i)));
}
Type List<capture#2-of ?>Method set(int, capture#2-of ?)Is an argument(int, capture#3-of ?)Not applicable to

To solve this, create a helper class that types wildcards.

package tryAny.effectiveJava;

import java.util.List;

public class GenericesTest9 {
    public static void swap(List<?> list, int i, int j) {
        swapHelper(list, i, j);
    }

    // Private helper method for wildcard capture
    private static <E> void swapHelper(List<E> list, int i, int j) {
        list.set(i, list.set(j, list.get(i)));
    }

}

This makes the implementation a bit more complicated, but allows the user to see a simple swap method.

Recommended Posts

Item 31: Use bounded wildcards to increase API flexibility
How to use Chain API
Item 41: Use marker interfaces to define types
How to use Java API with lambda expression
Summary of Java communication API (3) How to use SocketChannel
Summary of Java communication API (2) How to use HttpUrlConnection