Java Generics: how does method output work when a wildcard is used in method parameters?

Suppose 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, consider paragraph 5, in which you see the output at work. Now section 5 of the section is a working section.

If this is what it is, then why are there errors in sentences (1) and (2)?

In my view, all these calls from the three methods have the same conclusion, since the call to the abc method does not use the actual type parameters.

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

Please keep in mind that the abc () and def () method are my method. The compiler does not know what I want to do with the list in this method. I can simply print the size of the list or not do anything at all, as shown above. Therefore, there is no participation or participation.

CONTINUED → This is 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 that the second parameter is a list instead of Integer.

Point 20 - it is normal that cos T means an Object. Article 22 is in order. For the same reason as in paragraph 20.
Failed to complete paragraph 21? T could also be defined as an object too - will it also work?

+8
java generics
source share
5 answers

In each case, you created a little straw man by creating a LinkedList<Object> . This can be difficult. What you should remember is that when the compiler receives these method calls, it does not know that you created the LinkedList<Object> . For example, it could be a LinkedList<Integer> .

So, 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 super types. abc(c, "Hello"); Integer i1 = integers.get(0); Integer i2 = integers.get(1); 

Not much to see abc implementation:

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

To initialize i1 , you will get a ClassCastException .

In my view, all these calls from the three methods have the following output, created because the actual type parameters are not used in the call to the static abc method.

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

This is totally wrong. It is not assumed that T Object in any of your examples, even in the case ? super Object ? super Object . T is allowed to capture a, and if you cannot assign String to this capture (as if it is a ? super Object ), you will have a type error.

Edit # 1

Regarding your update (I replaced your shared array with List<T> , since shared arrays unnecessarily obscure the problem):

 // 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 true. The important mistake you make is here:

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

This is not exactly what the capture engine does or should transfer your call. He decides T how <? extends Object> <? extends Object> , but how is one specific capture of <? extends Object> <? extends Object> . Let me call him capture-1-of<? extends Object> capture-1-of<? extends Object> . So your method should 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 be allowed for 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 ppp call (although they are clearly the same capture):

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

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

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

Only reliable way which you could call ppp with a wildcard was to call it like this:

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

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

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

You can really call it like this:

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

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

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

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

This follows the rules that Sean put on his PECS post

If (your method) creates , use extends , if it consumes , use super ... p>

Edit # 2

Regarding your last change:

 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 not a valid output for T I think this is what you are missing, so I will say it clearly:

A List<Integer> NOT assigned to type List<Object>

Not at all. If so, you can do something similar, which clearly 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 you would have to assign List<Integer> variable of type List<Object> .

+1
source share

The difficulty in the wildcard is to implement ? extends Foo ? extends Foo does not mean "everything that extends Foo", but instead it means "some specific type that extends Foo." And since you are outside this definition, you have no way of knowing what specific subtype of Foo it is.

Update:

As I said, this is complicated. Here are some comments on your code.

 // a list of some specific type, and you don't know what type that is. // it 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 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 unsolvable 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 can shed light on the problem:

What is PECS (producer extends consumer super)?

+5
source share

The wildcard would then cause subtype covariance

I would say: “Try to imitate”, because even after using wild-cards you cannot get the same functionality as for arrays.

Then the question arises: why does sentence (3) work, and not sentence (2) or (1)?

Consider the first ad:

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

This declaration effectively says that I really do not know (or do not care) about which element is contained in collection a . This actually disconnects you from “mutating” the collection, as you can add elements of a type that are not actually compatible with a . You may have List<?> a = new ArrayList<String>() , but you still cannot insert anything into it. Basically, if adding is allowed, the compiler cannot guarantee the security of the collection type.

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

Here you say that b is a collection containing elements that extend Object . Which element you do not know. This again, in accordance with the previous discussion, does not allow you to add anything, since you can compromise type security.

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

Here you say: c is a collection containing elements of type Object , and these are superclasses or, in other words, at least Object . Since each reference type in Java is an assignment compatible with Object , it works in the third case.

0
source share

List<?> a means that a contains a certain type that 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> List<? extends Object> means essentially the same as List<?> , and will result in the same error.

List<? super Object> List<? super Object> means that the list contains a specific but unknown supertype of Object , and a parameterized method can accept any object that is-a Object for the second parameter. Although a method call is type safe, trying to assign an unsafe value to c will result in 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 */ 
0
source share

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

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

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

0
source share

All Articles