Capture Conversion – Generics

Capture Conversion

Consider the following non-generic method which does not compile:

Click here to view code image


static void fillWithFirstV1(List<?> list) {
    Object firstElement = list.get(0);       // (1)
    for (int i = 1; i < list.size(); i++)
      list.set(i, firstElement);             // (2) Compile-time error
  }

The method should fill any list passed as an argument with the element in its first position. The call to the set() method at (2) is not permitted, as a set operation is not possible with a <?> reference (see Table 11.3, p. 590). Using the unbounded wildcard ? to parameterize the list does not work. We can replace the wildcard with a type parameter of a generic method, as follows:

Click here to view code image

static <E> void fillWithFirstV2(List<E> list) {
    E firstElement = list.get(0);            // (3)
    for (int i = 1; i < list.size(); i++)
      list.set(i, firstElement);             // (4)
  }

Since the type of the argument is List<E>, we can set and get objects of type E from the list. We have also changed the type of the reference firstElement from Object to E in order to set the first element in the list.

It turns out that if the first method fillWithFirstV1() is reimplemented with a call to the generic method fillWithFirstV2(), it all works well:

Click here to view code image

 
static void fillWithFirstV3(List<?> list) {
    fillWithFirstV2(list);                   // (5) Type conversion
  }

The wildcard in the argument of the fillWithFirstV3() method has a type capture. In the call to the fillWithFirstV2() method at (5), this type capture is converted to the type E. This conversion is called capture conversion, and it comes into play under certain conditions, which are beyond the scope of this book.

11.10 Flexibility with Wildcard Parameterized Types

Nested Wildcards

In this subsection, the examples make use of type parameters that are specified for the interfaces Collection<E>, Set<E>, and Map<K,V> in the java.util package (Chapter 15, p. 781). In the discussion below, the important fact to keep in mind is that the interface Set<E> is a subinterface of Collection<E>.

We have seen that the subtype relationship is invariant for the unbounded type parameter <T>:

Click here to view code image

Collection<Number> colNum;
Set<Number> setNum;
Set<Integer> setInt;
colNum = setNum; // (1) Set<Number> <: Collection<Number>
colNum = setInt; // (2) Compile-time error!

The same is true when concrete parameterized types are used as actual type parameters, implementing what are called nested parameterized types—that is, using parameterized types as type parameters.

Click here to view code image

Collection<Collection<Number>> colColNum; // Collection of Collections of Number
Set<Collection<Number>>        setColNum; // Set of Collections of Number
Set<Set<Integer>>              setSetInt; // Set of Sets of Integer
colColNum = setColNum;                    // (3) Set<Collection<Number>> <:
                                          //     Collection<Collection<Number>>
colColNum = setSetInt;                    // (4) Compile-time error!
setColNum = setSetInt;                    // (5) Compile-time error!

Again, we can use the upper bounded wildcard to induce subtype covariance. The upper bounded wildcard is applied at the top level in the code below. The assignment below at (8) is not compatible because Set<Set<Integer>> is not a subtype of

Click here to view code image

Collection<? extends Collection<Number>> colExtColNum;
colExtColNum = colColNum;       // (6) Collection<Collection<Number>> <:
                                //     Collection<? extends Collection<Number>>
colExtColNum = setColNum;       // (7) Set<Collection<Number>> <:
                                //     Collection<? extends Collection<Number>>
colExtColNum = setSetInt;       // (8) Compile-time error!

In the code below, the wildcard is applied at the innermost level:

Click here to view code image

Collection<Collection<? extends Number>> colColExtNum;
colColExtNum = colColNum;       // (9)  Compile-time error!
colColExtNum = setColNum;       // (10) Compile-time error!
colColExtNum = setSetInt;       // (11) Compile-time error!

The assignments above show that the upper bounded wildcard induces subtype covariance only at the top level. At (9), type A (=Collection<Number>) is a subtype of type B (=Collection<? extends Number>), but because a subtype covariance relationship does not hold between parameterized types, the type Collection<A> (=Collection <Collection<Number>>) is not a subtype of Collection<B> (= Collection<Collection<? extends Number>>).

The above discussion also applies when a parameterized type has more than one type parameter:

Click here to view code image

Map<Number, String>  mapNumStr;
Map<Integer, String> mapIntStr;
mapNumStr = mapIntStr;             // (12) Compile-time error!

Again, the upper bounded wildcard can only be used at the top level to induce subtype covariance:

Click here to view code image Map<Integer, ? extends Collection<String>> mapIntExtColStr;
Map<Integer, Collection<? extends String>> mapIntColExtStr;
Map<Integer, Collection<String>>           mapIntColStr;
Map<Integer, Set<String>>                  mapIntSetStr;
mapIntExtColStr = mapIntColStr;// (13) Map<Integer, Collection<String>> <:
                               //      Map<Integer, ? extends Collection<String>>
mapIntExtColStr = mapIntSetStr;// (14) Map<Integer, Set<String>> <:
                               //      Map<Integer, ? extends Collection<String>>
mapIntColStr    = mapIntSetStr;    // (15) Compile-time error!
mapIntColExtStr = mapIntColStr;    // (16) Compile-time error!
mapIntColExtStr = mapIntSetStr;    // (17) Compile-time error!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *