Auro
Auro

Reputation: 1628

Trying to understand the role of super wildcard in Java Generics

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

Answers (3)

Val M
Val M

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

boot-and-bonnet
boot-and-bonnet

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

rgettman
rgettman

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

Related Questions