Given:
public class Testcase {
public static <E> List<List<E>> transform(List<List<E>> list) {
return list;
}
public static <E> List<List<? extends E>> transform2(List<List<? extends E>> list) {
return list;
}
public static void main(String[] args) {
List<List<Integer>> known = new ArrayList<>();
List<List<? extends Number>> unknown = new ArrayList<>();
transform(known); // works
transform(unknown); // fails
transform2(known); // fails
transform2(unknown); // works
}
}
The compiler accepts transform(known)
but complains:
cannot infer type-variable(s) E
(argument mismatch; List<List<? extends Number>> cannot be converted to List<List<E>>)
where E is a type-variable:
E extends Object declared in method <E>transform(List<List<E>>)
for transform(unknown)
. I get the opposite problem for transform2()
. I've consulted PECS and I believe that transform()
is the correct method declaration but I can't for the life of my figure out how to get a single method to handle both cases.
Note that this problem only occurs for multi-level Generics. List<? extends Number>
works just fine. The problem is not specific to Lists. You'll get it for Future<Task<X>>
and so on.
What method declaration will handle both bounded and unbounded Generics? And if it's not possible, why?
Bounded and unbounded wildcards in Generics are two types of wildcards available on Java. Any Type can be bounded either upper or lower of the class hierarchy in Generics by using bounded wildcards.
If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class. These are known as bounded-types in generics in Java.
Unbounded wildcards An unbounded wildcard is the one which enables the usage of all the subtypes of an unknown type i.e. any type (Object) is accepted as typed-parameter. For example, if want to accept an ArrayList of object type as a parameter, you just need to declare an unbounded wildcard.
A bound is a constraint on the type of a type parameter. Bounds use the extends keyword and some new syntax to limit the parameter types that may be applied to a generic type. In the case of a generic class, the bounds simply limit the type that may be supplied to instantiate it.
The most specific type for which this works seems to be ? extends List<? extends ?>
:
class Testcase {
public <E> List<List<E>> transform(List<List<E>> list) {
return list;
}
public <E> List<List<? extends E>> transform2(List<List<? extends E>> list) {
return list;
}
public <E> List<? extends List<? extends E>> transform3(List<? extends List<? extends E>> list) {
return list;
}
public void test(String[] args) {
List<List<Integer>> known = new ArrayList<>();
List<List<? extends Number>> unknown = new ArrayList<>();
transform(known); // works
// transform(unknown); // fails
// transform2(known); // fails
transform2(unknown); // works
transform3(known);
transform3(unknown);
}
}
Here is an explanation why this makes sense.
A List<? extends Number>
is clearly not a List<E>
for any E
, because it doesn't have to be able to insert instances of the most general E
, thus the first definition fails.
A List<Integer>
conforms to List<? extends Number>
, but this does not help, because these types are still unequal, therefore types List<List<Integer>>
and List<List<? extends Number>>
are completely unrelated. Therefore, the second definition also fails.
What you want instead is the use-site least upper bound of List<List<Integer>>
and List<List<? extends Number>>
. You can obtain it using the following rule: the use-site-LUB of List<A>
and List<B>
is:
USLUB(List<A>, List<B>) = List<? extends USLUB(A, B)>
Now, step by step:
A = Integer
and B = ? extends Number
, the least upper bound is ? extends Number
, because Integer
conforms to ? extends Number
.List<Integer>
and List<? extends Number>
the least upper bound becomes ? extends List<? extends Number>
List<? extends List<? extends Number>>
.Hurray for use-site variance ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With