Reputation: 1628
I was going through this article to understand the role of super
wildcard in generics. I understood how extends
works, but I am having difficulty understanding super
I have a ClassA
that is extended by ClassB
, making ClassA
super class of ClassB
.
If I understood the article correctly, the <? super ClassB>
would allow any Class that is a super type of ClassB
.
I have the following code
GenericMethod.java
public class GenericMethod<T> {
private List<T> list;
public GenericMethod() {
list = new ArrayList<>();
}
public void add(T t) {
list.add(t);
}
public T get(int index) {
return list.get(index);
}
}
Driver.java
public class Driver {
public static void main(String[] args) {
GenericMethod<? super ClassB> genericMethod = new GenericMethod<>();
ClassA classA = new ClassA();
genericMethod.add(classA); // Compile-time error here
}
}
Error
The method add(capture#1-of ? super ClassB) in the type GenericMethod<capture#1-of ? super ClassB> is not applicable for the arguments (ClassA)
I don't understand where I am going wrong. When I instantiated the GenericMethod
class, I already declared that it would accept any value that is a super type of ClassB
with the declaration <? super ClassB>
. Thus, the T
inside the GenericMethod
class should accept all classes that ClassB
extends.
Why does the add
method throw the compile-time error then? Shouldn't the method add
already know that it's being passed a perfectly compatible type?
Upvotes: 4
Views: 1420
Reputation: 617
I admit that it is a bit counterintuitive, but from the point of view of the compiler, it makes sense.
Suppose you define a list in this way:
List<? super ClassB> myList;
And you got
ClassB extends ClassA;
ClassC extends ClassA;
Could you do ?
ClassA a = new ClassC();
myList.add (a);//does not compile
No, by several reasons. First because finally, at runtime, the parametrized type becomes a concrete and single type. So, in practice you cannot mix ClassA objects with ClassB objects.
And second, because the compiler must check that whatever you insert is casteable to ClassB. So, only adding a ClassB object (or a subtype of him), fulfil the contract. But the opposite is not always true! A ClassA object could no be a super type of ClassB.
I.e. the object "a" is actually a ClassC object!
That is why you cannot add a supertype. So it only lets you add ClassB objects -or subtypes-
If you cast the object "a" to ClassB, would be compile, but will fail at runtime getting a ClassCastException:
ClassA a = new ClassC();
myList.add((ClassB) a); //Compiles but fails at runtime
At the same time, the wildcard combined with super, can be used as a formal parameter of a method. I.e.:
void printValues (List<? super ClassB> list)
This way permits generelize the treatment of super ClassB collections, making possible this calls:
List<Object> lo;
List<ClassA> la;
...
printValues (lo);
printValues (la);
In this case, the definition using wildcards like <? super ClassB>, makes a little more sense.
Upvotes: 0
Reputation: 761
By declaring GenericMethod<? super ClassB>
you are declaring the type is an unknown type that is a super-class of ClassB (or ClassB itself). And asking the compiler to only allow subtypes of this unknown type to be added to the list.
The only compatible subtypes the compiler knows are ClassB and any subtypes of ClassB. When creating instances, it is usually better to avoid wildcards.
For method parameters, wildcards give you more flexibility on what can be accepted. Use PECS
(producer=extends, consumer=super) to determine which to use.
See Slide 5
Upvotes: 1
Reputation: 178263
The ? super
clause is a lower-bound wildcard. But the bound is on the type parameter that is inferred, not a restriction on the types of arguments that can be passed to a method that takes a parameter of that generic type.
When you say <? super ClassB>
, you indicate that the type parameter can be ClassB
or any supertype, e.g. ClassA
or Object
.
The compiler must treat the add
method as if it could be any of these signatures:
add(Object t)
add(ClassA t)
add(ClassB t)
(There could be other types if ClassA
inherited directly from another class instead of Object
).
The compiler must reject a ClassA
as an argument to add
because the type parameter could be inferred as ClassB
. It's legal to assign a GenericMethod<ClassB>
to your genericMethod
variable.
GenericMethod<? super ClassB> genericMethod = new GenericMethod<ClassB>();
But it doesn't make sense to be able to pass a ClassA
to a method that expects a ClassB
.
In fact that is what is inferred by the diamond operator - ClassB
.
Your confusion is in the conflation of two concepts: what type parameters are allowed and what types of objects are allowed in methods that use a type parameter. Using a wildcard restricts the type parameter, but the method still accepts types that are the type parameter or a subtype.
Upvotes: 1