Capture Conversion
Consider the following non-generic method which does not compile:
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:
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:
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>:
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.
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
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:
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:
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!
Leave a Reply