Java Generics: How does method inference work when wildcard is being used in the method parameters?

Supposedy i have the following:

class x {

public static void main(String [] args) {
    List <?> a = new LinkedList<Object>();
    List <? extends Object> b = new LinkedList<Object>();
    List <? super Object> c = new LinkedList<Object>();
    abc(a, "Hello"); // (1) Error
    abc(b, "Hello"); // (2) Error
    abc(c, "Hello"); // (3) ok  
    def(b); // (4) ok

// Showing inference at work
    Integer[] a = {10, 20, 30};  // (5)
    T is inferred to be ? extends Object
    Method signature: ppp(? extends Object, ? extends Object[])
    Method call signature: ppp(String, Integer[]);
    ppp("Hello", a); // ok

}

static <T> void abc(List<T> a, T b) {}  
static <T> void def(List<T> a) {}
static <T> void ppp(T t1, T[] t2){}

}  

To begin with, look at clause 5 showing inference at work. Now clause 5 section is a working section.

If that is what it is, then why does clause (1) & (2) have errors?

From my view, all these 3 methods calling have the same inference generated since no actual type parameters is used on the abc method call.

method parameter <T> abc (List <T> a, T b>)
inferred <Object> abc (List <Object>, Object) // (4)

Please bear in mind, method abc() and def() is my method. Compiler doesn’t know what i want to do with the List in this method. I might just print the list size or might not even do anything at all as shown above. So there is no get or set involved here.

CONTINUATION –> This is getting very confusing for me.

class y {

public static void main(String [] args) {
    List <Integer> a = new LinkedList<Integer>();
    List <Object> b = new LinkedList<Object>();
    ppp("Hello", new Integer(1)); // (20) ok
    qqq("Hello", a); // (21) error
    qqq("Hello", b); // (22) ok
}

static <T> void ppp(T t1, T t2) {}
static <T> void qqq(T t1, List <T> t2) {}
}

Note that clause 21 is the same as clause 20 except 2nd parameter is being made to be a List instead of Integer.

Clause 20 is ok cos’ T is inferred to be Object.
Clause 22 is ok. Same reason as clause 20.
Clause 21 failed? T could also be inferred to be Object too – would work too?

Answer

You’ve set up a bit of a straw man by creating a LinkedList<Object> in each case. That can make it difficult to see the problem. What you have to remember is that when the compiler gets to those method invocations, it doesn’t know that you created a LinkedList<Object>. It could be a LinkedList<Integer>, for example.

So let’s look at your code with more interesting initializations:

List<Integer> integers = new LinkedList<Integer>();

List <?> a = integers;
List <? extends Object> b = integers;
List <? super Object> c = new LinkedList<Object>();

//INVALID.  T maps to a type that could be Object OR anything else.  "Hello"
//would only be type-assignable to T if T represented String, Object, CharSequence,
//Serializable, or Comparable
abc(a, "Hello");

//INVALID.  T maps to a type that could be Object OR anything else.  "Hello"
//would only be type-assignable to T if T represented String, Object, CharSequence,
//Serializable, or Comparable
abc(b, "Hello");

//VALID.  T maps to an unknown super type of Object (which can only be Object itself)
//since String is already type-assignable to Object, it is of course guaranteed to be 
//type-assignable to any of Object's super types. 
abc(c, "Hello");

Integer i1 = integers.get(0);
Integer i2 = integers.get(1);

It doesn’t take much to see that if the implementation of abc was this:

//a perfectly valid implementation
static <T> void abc(List<T> a, T b) {
   a.add(b);
}

That you would get a ClassCastException when initializing i1.

From my view, all these 3 methods calling has the following inference generated since no actual type parameters is used on the abc static method call.

method parameter <T> abc (List <T> a, T b>)
inferred <Object> abc (List <Object>, Object) // (4) 

This is categorically wrong. It is not inferred that T is Object in any of your examples, not even in the case of ? super Object. T is resolved to the capture of a, and unless you can assign a String to that capture (as is the case when it’s ? super Object) you will have a type error.

Edit #1

Regarding your update (I’ve replaced your generic array with a List<T> since generic arrays needlessly cloud the issue):

// Showing inference at work
List<Integer> a = Arrays.asList(10, 20, 30);  // (5)
T is inferred to be ? extends Object
Method signature: ppp(? extends Object, List<? extends Object>)
Method call signature: ppp(String, List<Integer>);
ppp("Hello", a); // ok

This is not correct. The crucial mistake you’re making is here:

Method signature: ppp(? extends Object, List<? extends Object>)

This is not at all what the capture engine does or should translate your invocation into. It resolves T as <? extends Object> but as one specific capture of <? extends Object>. Let’s call it capture-1-of<? extends Object>. Thus your method must be like this:

Method signature: ppp(capture-1-of<? extends Object>, List<capture-1-of<? extends Object>>)

This means that there is a binding between the two parameters…they must resolve to the same capture. In general it is very difficult to tell the compiler that two things are the same capture. In fact, even this is not a valid invocation of ppp (even though they are clearly the same capture):

List<? extends Integer> myList;
ppp(myList.get(0), myList);

One way we could invoke ppp is through a generic intermediary:

public static <T> void pppCaller(List<T> items) {
    ppp(items.get(0), items);
}

pppCaller(myList);

The only sure-fire way you could invoke ppp with a wildcarded list would be to invoke it like this:

List<? extends Integer> myList = new ArrayList<Integer>();
ppp(null, myList);

That’s because the null is the only thing that you can assign to anything. On the other hand, if you had this method:

private static <T> void qqq(T item1, T item2) {}

You could indeed invoke it like this:

List<? extends Integer> myList;
qqq(myList.get(0), myList.get(1));

Because in this case, the inference can generalize T to Object. Since List<? extends Integer> is not covariant with List<Object>, it cannot do the same for ppp().

However, what most people do to get around this is to relax their method signature. Instead, declare ppp as the following:

public static <T> ppp(T item, List<? super T> items) {
}

This follows the guidelines that Sean put in his post of “PECS”

If (your method) produces, use extends, if it consumes, use super.

Edit #2

Regarding your latest edit:

public static void main(String [] args) {
    List <Integer> a = new LinkedList<Integer>();
    qqq("Hello", a); // (21) error
}

static <T> void qqq(T t1, List <T> t2) {}

Object is not a valid inference for T. I think this is something fundamental you’re missing, so I’ll say it clear:

A List<Integer> is NOT type-assignable to List<Object>

Not at all. If it were, you could do something like this which obviously violates type safety:

List<Integer> myInts = new ArrayList<Integer>();
List<Object> myObjects = myInts; //doesn't compile!
myObjects.add("someString");
Integer firstInt = myInts.get(0); //ClassCastException!

So T cannot be inferred as Object, since it would require assigning a List<Integer> to a variable of type List<Object>.

Leave a Reply

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