Java Generics: Wildcard capture misunderstanding

why the compiler can't retain the assignment safe? It knows that,by executing for instance, the method with an Integer List, it gets from i.get an Integer value. So it try to set an Integer value at index 0 to the same Integer list (i).

Put differently, why does the compiler not know that the two usages of the wildcard type List<?> in

i.set(0, i.get(0));

refer to the same actual type?

Well, that would require the compiler to know that i contains the same instance for both evaluations of the expression. Since i isn't even final, the compiler would have to check whether i could possibly have been assigned in between evaluating the two expressions. Such an analysis is only simple for local variables (for who knows whether an invoked method will update a particular field of a particular object?). This is quite a bit of additional complexity in the compiler for rarely manifesting benefits. I suppose that's why the designers of the Java programming language kept things simple by specifying that different uses of the same wildcard type have different captures.


why the compiler can't retain the assignment safe?

The compiler doesn't know anything about the type of elements in List<?> i, by definition of ?. Wildcard does not mean "any type;" it means "some unknown type."

It knows that,by executing for instance, the method with an Integer List, it gets from i.get an Integer value.

That's true, but as I said above: the compiler can only know – at compile time, remember – that i.get(0) returns an Object, which is the upper bound of ?. But there's no guarantee that ? is at runtime Object, so there is no way for the compiler to know that i.set(0, i.get(0)) is a safe call. It's like writing this:

List<Foo> fooz = /* init */;
Object foo = fooz.get(0);
fooz.set(0, foo); // won't compile because foo is an object, not a Foo

More reading:

  • Can't add value to the Java collection with wildcard generic type
  • Java Collections using wildcard
  • Generic collection & wildcard in java
  • Generics - Cannot add to a List with unbounded wildcard
  • What is the difference betwen Collection<?> and Collection<T>

According to Get-Put principle:

  1. If you have extends wildcard as in List<? extends Something>, then:

    1A. You can get from the structure using Something or its superclass reference.

    void foo(List<? extends Number> nums) {
       Number number = nums.get(0); 
       Object number = nums.get(0); // superclass reference also works.
    }
    

    1B. You cannot add anything to the structure (except null).

    void foo(List<? extends Number> nums) {
       nums.add(1); Compile error
       nums.add(1L); Compile error
       nums.add(null); // only null is allowed.
    }
    
  2. Similarly, if you have super wildcard as in List<? super Something>, then:

    2A. You can add to the structure which is Something or its subclass. For eg:

    void foo(List<? super Number> nums) {
        nums.add(1); // Integer is a subclass of Number
        nums.add(1L); // Long is a subclass of Number
        nums.add("str"); // Compile error: String is not subclass of Number         
    }
    

    2A. You cannot get from the structure (except via Object reference). For eg:

    void foo(List<? super Integer> nums) {
        Integer num = nums.get(0); // Compile error
        Number num = nums.get(0); // Compile error
        Object num = nums.get(0); // Only get via Object reference is allowed.        
    }
    

Coming back to OP's question, List<?> i is just the short representation for List<? extends Object> i. And since it's a extends wildcard, the set operation fails.

The final piece remaining is WHY the operation fails ? Or why the Get-Put principle in the first place? - This has to do with type safety as answered by Jon Skeet here.


I also find this question hard to understand; splitting the single command into two commands helped me.

Below code is what actually happens in the background when the original method is inspected&compiled, the compiler makes its own local variable: the result of the i.get(0) call is placed in the register on the local variable stack.

And that is - for the understanding of this issue - the same as making a local variable which for convenience I have given the name element.

import java.util.List;
public class WildcardError {
 void foo(List<?> i) {
  Object element = i.get(0);  // command 1
  i.set(0, element);          // command 2
 }
}

When command 1 is inspected, it can only set the type of element to Object (--> upperbound concept, see Matt's answer), as it can not use ? as a variable type; the ? is only used for indicating that the generic type is unknown.

Variable types can only be real types or generic types, but since you don't use a generic type in this method like <T> for example, it is forced to use a real type. This forcing is done because of the following lines in the java specification (jls8, 18.2.1):

A constraint formula of the form ‹Expression → T› is reduced as follows:

[...]

– If the expression is a class instance creation expression or a method invocation expression, the constraint reduces to the bound set B3 which would be used to determine the expression's invocation type when targeting T, as defined in §18.5.2. (For a class instance creation expression, the corresponding "method" used for inference is defined in §15.9.3).


Solution Will Be,

import java.util.List;

    public class WildcardError {

    private void fooHelper(List<T> i){
         i.set(0, i.get(0));
    }
    public void foo(List<?> i){
         fooHelper(i);
    }
}

here fooHelper will capture type T of wildcard ? (so as the name wildcard capture).