8

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?

yapkm01
  • 3,590
  • 7
  • 37
  • 62

5 Answers5

5

The hard thing about the wildcard is to realize ? extends Foo does not mean "anything that extends Foo", but instead it means "some specific type that extends Foo". And since you are outside that definition, you have no way to know which specific sub-type of Foo it is.

Update:

As I said, it's complicated. Here are some comments on your code.

// a list of some specific type, and you don't know what type that is.
// it's a sub-type ob Object, yes, which means that you can do
// Object foo = a.get(0); , but the compiler has no way of knowing
// whether it's a String so you can't pass in a String
List <?> a = new LinkedList<Object>();
// same here. '?' and '? extends Object' are equivalent
List <? extends Object> b = new LinkedList<Object>();
// this is a list of Objects and superclasses thereof.
// since there are no superclasses of Object, this is equivalent to
// List<Object>. And through inheritance, a String is an Object, so
// you can pass it in.
List <? super Object> c = new LinkedList<Object>();

Update 2:

The problem here is that you are dealing with fixed, but unresolveable variables.

// you can pass in a List<String> and  a String,
// but if you pass in a List<?>, the compiler has no idea what
// '?' is and just can't substitute 'String'.
// 'T' doesn't help you here, because 'T' can't match both
// '?' and 'String'.
static <T> void abc(List<T> a, T b) {}  
// this works because 'T' substitutes '?' and doesn't have to worry
// about a 2nd parameter
static <T> void def(List<T> a) {}

Read this question, it might shed some light on the problem:

What is PECS (Producer Extends Consumer Super)?

Community
  • 1
  • 1
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • 1
    That's right. But irregardless, that unknown subtype which extends Foo will always be a subtype of a cosmic supertype which is Object. – yapkm01 Sep 26 '11 at 15:23
  • @yapkm01 well yes, but potentially a different type from the one you are passing in (String) – Sean Patrick Floyd Sep 26 '11 at 15:25
  • My point is, when 2 parameters being used is totally different from one another (say number and string), the ultimate inference climbing back upwards is always Object. The way i look at it is as long as you don't supply an actual type parameter, using inference irregardless of any parameter types (which could be different from one another), it always goes back to Object. Object is the main holder for everything. So it should work right? – yapkm01 Sep 26 '11 at 15:28
  • I understand what you are saying. Look at my update above on a new method called def. Notice there is only 1 parameter. Since it only has 1 parameter, it doesn't need to do inference looking for a common superclass (needed for 2 or more parameters). Notice that ? extends Object works now (clause (4)). This error that this question is about i believe has to do with inference to find a common superclass. When it couldn't find, it generates an error. Now the issue for me is, Object is always a common superclass for any type. So this should work. – yapkm01 Sep 26 '11 at 15:54
  • By the way, bear in mind that > can be converted to a through capture conversion. So how come clause (1)&(2)&(3) doesn't get converted to a type through capture conversion. – yapkm01 Sep 26 '11 at 16:01
  • @yapkm01 *I understand what you are saying* almost, but not quite. read my update. – Sean Patrick Floyd Sep 26 '11 at 16:12
  • A capture conversion can happen from a wildcard to a type parameter (if you have 'A programmer's guide to Java SCJP 3rd edition by Mughal - the best SCJP book in my opinion', please refer to page 705 about capture conversion). – yapkm01 Sep 26 '11 at 16:36
  • @yapkm01 from one wildcard, yes. But you are trying to convert one wildcard and one fixed type to a common type and that just won't work – Sean Patrick Floyd Sep 26 '11 at 16:50
  • Yes. Just to elaborate a bit. I'm very well verse with the restriction rules on different wildcard. But here i think i found the answer to my confusion. Lets get the facts right first. Using 1 parameter on abc() - no problem irregardless of different bound. Using 2 parameters using inference no problem is both parameter can be deduced what they are before inference (hence clause (3)) works. A wildcard which cannot produce a definite type .. inference will not work. (But in my opinion could have been covered using Object?) – yapkm01 Sep 26 '11 at 16:56
  • @yap: It's not a problem with inference, only with type assignment. See the bottom part of my answer. In the case of `? super Object` it's not inferring that `T` is `Object`, it can just make more guarantees about `T` when it comes to assignment. – Mark Peters Sep 26 '11 at 16:58
  • clause (2) which failed on ? extends Objects using abc() with 2 parameters now work on clause (3). Doesn't that prove that it is the inference which is failing? – yapkm01 Sep 26 '11 at 17:03
  • @yap: What about the inference do you think is failing? Obviously if you pass in a `List>` to `add(List...)` the type engine can't infer that `T` is `Object` since that is quite simply **wrong** (as we have shown). – Mark Peters Sep 26 '11 at 17:22
1

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>.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • So the first 2 case failed due to inference. It cannot inferred an known wildcard and a string. Case 3 works since that lower bound wildcard is .. well Object. Object and string .. well common superclass would be Object. In conclusion can we say that inference needs definite some sort of type to work? – yapkm01 Sep 26 '11 at 17:16
  • @yapkm01: I'm not sure what you mean by "failed due to inference." They failed because they wouldn't be type safe, and there is no alternate inference that could have been made that would **make** it type safe. In all three cases T is capturing the wildcard, the difference is that in case 3 it has a definite lower bound and thus you can assign something to a `T`. On the other hand, if the *result* was a `T` and you were assigning *that* to a String, then the upper bound is relevant. If you don't have a lower or upper bound, than you are severely limited in what you can do. – Mark Peters Sep 26 '11 at 17:20
  • Please see my update. It shows inference at work. If you look at this concept, the same concept could be applied to clause 1-3. So i don't really see the actual problem unless inference requires a known type for one of its parameter in order for it work. The way i look at this now, this problem above has nothing to do with type safety (tho' yes generic is all about type safety using restriction). But in this particular case, it cannot do inference because one of it's parameter is unknown (tho' like i said Object could be used as the final straw) – yapkm01 Sep 26 '11 at 18:38
  • You can refer to page 701 on 'A Programmers guide to JAVA SCJP by Mughal 3rd edition' – yapkm01 Sep 26 '11 at 18:47
  • I'm so confused. Maybe need to take some time to swallow this. LOL – yapkm01 Sep 26 '11 at 18:54
  • @yapkm01: It's tough material and sorry we haven't had the ability to explain it yet. I find it hard to describe generics to others in a clear way. I've updated my answer again to respond to your update. However, I think introducing generic arrays clouds the issue as they are very bad practice and have some other nuances. So I replaced the array with a list. – Mark Peters Sep 26 '11 at 19:18
  • I understand now on the issue on class x using wilcard. However i still cant figure out for class y, why clause 22 is ok but clause 21 failed? A type parameter T being Object should work for clause 21. – yapkm01 Sep 26 '11 at 20:53
  • @yapkm01: I've responded to that in my edit. I think you really need to focus on what I'm saying there because it seems to be a fundamental misunderstanding. You **cannot** assign a `List` to a `List`. – Mark Peters Sep 26 '11 at 20:59
  • Yes. Forgotten about it. Just too tired. Thanks a zillion – yapkm01 Sep 26 '11 at 21:03
0

A wildcard would then needed to induce subtype covariance

I'd rather say "try to simulate" since even after using wild-cards you can't get the same functionality you get for arrays.

Then the question is why clause (3) works and not clause(2) or (1)?

Consider the first declaration:

List <?> a = new LinkedList<Object>();

This declaration effectively says, I really don't know (or care) what kind of element the collection a contains. This effectively shuts you off from "mutating" the collection since you might end up adding elements of type which are not really compatible with a. You can have List<?> a = new ArrayList<String>() but you still won't be able to put anything in it. Basically, in case an add is allowed, the compiler can't guarantee the type safety of the collection.

List <? extends Object> b = new LinkedList<Object>();

Here you say b is a collection which contains elements which extend an Object. What kind of element, you don't know. This again as per the previous discussion doesn't allow you to add anything since you could end up compromising type safety.

List <? super Object> c = new LinkedList<Object>();

Here you say, c is a collection which contains elements of type Object and it's super-classes or in other words, at least an Object. Since each reference type in Java is assignment compatible with Object, it works in the third case.

Sanjay T. Sharma
  • 22,857
  • 4
  • 59
  • 71
0

List<?> a means that a holds a specific type, which is unknown. Consider this more complete example:

List<Float> floats = new ArrayList<Float>();
List<?> a = floats; /* Alias "floats" as "a" */
abc(a, "Hello"); /* This won't compile... */
float f = floats.get(0); /* .. if it did, you'd get a ClassCastException */

static <T> abc(List<T> a, T b) {
  a.add(b); /* Absolutely no problem here. */
}

List<? extends Object> means essentially the same thing as List<?>, and would cause the same error.

List<? super Object> means that the list holds a specific, but unknown super-type of Object, and the parameterized method can accept any object that is-a Object for the second parameter. While the method invocation is type-safe, attempting to assign an unsafe value to c will cause an error:

List<Number> numbers = new ArrayList<Number>();
List<? super Object> a = numbers; /* This won't compile... */
abc(a, "Hello");
Number n = numbers.get(0); /* ...if it did, you'd get a ClassCastException */
erickson
  • 265,237
  • 58
  • 395
  • 493
  • I understand everything above but here's where the issue is. Method abc is doing nothing. How does the compiler knows i want to add? Maybe i just want to do a get. You see, in a collection this restriction is done on the collection method itself like list.add() but this particular method abc() well it's mine. I could write any code inside or even might leave it blank as you can see above. This method abc() is not a collection method. It's my own. There's no gurantee in this method abc() i want to add any records to that list. Maybe i want to do just a print or just to get the size(). – yapkm01 Sep 26 '11 at 16:10
  • @yapkm01: The compiler **doesn't** know what you are doing. That's why code which you can see logically is type-safe (like invoking a method that does nothing) won't compile. You can see it's safe, but the compiler isn't analyzing what you are doing, it's just enforcing basic rules about type-safety. If you *could* do something unsafe, it won't compile. It doesn't matter whether you *really are* doing something unsafe. – erickson Sep 26 '11 at 16:39
0

Integer[] is a subtype of Object[], however List<Integer> is not a subtype of List<Object>. This is quite confusing; arrays are more primitive and should be avoided.

If a method parameter type is T[], it accepts all S[] where S is a subtype of T.

To do this with List, the type should be List<? extends T>. It accepts all List<S> where S is a subtype of T.

irreputable
  • 44,725
  • 9
  • 65
  • 93