25

Reading the Java online tutorial I haven't understood anything about wildcard capture. For example:

    import java.util.List;
    public class WildcardError {
     void foo(List<?> i) {
      i.set(0, i.get(0));
     }
    }

Why can't the compiler retain the assignment safely?

It knows that, by executing for instance, the method with an Integer List, it gets from i.get an Integer value. So it tries to set an Integer value at index 0 to the same Integer list (i).

So, what's wrong? Why write Wildcard helper?

K. hervey
  • 119
  • 2
  • 13
  • 1
    possible duplicate of [Can't add value to the Java collection with wildcard generic type](http://stackoverflow.com/questions/3716920/cant-add-value-to-the-java-collection-with-wildcard-generic-type) – Matt Ball Aug 20 '12 at 19:51
  • Erm, isn't it kind of inconsistent to vote to close the question *and* provide an answer? – meriton Aug 20 '12 at 20:05
  • It is not a duplicate (at least not of that question). – herman Aug 20 '12 at 23:04
  • I've seen questions about this exact example before, but can't seem to find them in search. – Paul Bellora Aug 20 '12 at 23:11
  • possible duplicate of [Capturing wildcards in java generics](http://stackoverflow.com/questions/17340474/capturing-wildcards-in-java-generics) – thecoop Nov 01 '13 at 12:52
  • I just wrote an [answer](http://stackoverflow.com/questions/30797805/understanding-a-captured-type-in-java/30798066#30798066) which explains wildcard capture for another question. That information seems relevant for this question also. – Lii Jun 12 '15 at 08:39
  • http://bayou.io/draft/Capturing_Wildcards.html – ZhongYu Jan 25 '16 at 18:48

7 Answers7

28

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.

meriton
  • 68,356
  • 14
  • 108
  • 175
27

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:

Community
  • 1
  • 1
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • 7
    The answer of @meriton is more correct. The compiler doesn't have to know anything about the actual runtime type of the elements. If `i` was guaranteed to be the same instance in both cases, the (unknown) return type of the `get` method would be the same as the (unknown) 2nd argument type of the `set` method, so it would compile. However, as meriton pointed out, this guarantee is never assumed (even if `i` were local or final). – herman Aug 20 '12 at 23:01
  • Only in this special case - adding an element to the collection from whence it came - is that true. In general, [you can _never_ add to a wildcarded collection](http://stackoverflow.com/questions/3716920/cant-add-value-to-the-java-collection-with-wildcard-generic-type) because of the [PECS'd method signatures](http://stackoverflow.com/questions/2248390/java-generics-collections-max-signature-and-comparator/2248503#2248503) involved. – Matt Ball Aug 21 '12 at 02:20
  • 1
    Sure, but this was precisely the question. – herman Aug 27 '12 at 16:10
  • 1
    @herman This answer doesn't state that the compiler should know `i`'s runtime type. This answer is absolutely correct and explains what's meaning of unknown type—a definition of wildcard. @meriton's answer is also correct too, but has a focus on different perspective. – rosshjb Dec 12 '21 at 13:55
  • 2
    @rosshjb The question concerns a local variable in which case the compiler could actually infer that both wildcards have the same type even without knowing exactly which type that is. meriton 's answer clarifies that the language designers chose not to do that, while this answer specifically states that there is no way for the compiler to know, which is incorrect. It would be correct in case of wildcard usages for a member variable or two separate variables, but not for the case in this particular question. – herman Dec 13 '21 at 17:19
4

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.

Sumit Jha
  • 2,095
  • 2
  • 21
  • 36
2

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

Wouter Vegter
  • 1,093
  • 2
  • 11
  • 11
1

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

Patrick
  • 1,717
  • 7
  • 21
  • 28
dhanu10896
  • 315
  • 2
  • 16
1

I think you misunderstand the ? in Generics. It isn't "wildcard capture" at runtime. It is "wildcard capture" at compile time. Since that capture is gone after compilation, the List<?> often becomes List<Object> or a list of some interface or common base class that is the lowest subclass that is shared amongst the instances according to the Generics type.

Because Java had to make the Generics system compatible with non-Generics supporting JVMs, there are no Generics types at runtime. This means that all checks / captures don't exist at runtime. Either the compiler permits a very narrow range of compatible Generics type assignments when it compiles, or it throws an error.

Now if you want to hold a Generics type at runtime, there are approaches. Most of them involve passing the Class object as a parameter into a constructor, which holds it in a field. Then you can refer to the field for the exact passed type. You can entangle this type with your Generics parameters; and by doing so, you can then have the actual type in places in the runtime code where the "Generics type parameters" have been replaced by their most permissive type supported in the real Java type system. I don't recommend doing this for many situations, as it is a lot of work (and not likely to give you much more security); but, occasionally you really do need the type.

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
0

I guess your misunderstanding of the restriction comes from substitution of ? for any type, Object or something like that in your mind. But that assumption is not correct, ? in fact means unknown type. So in the following line

fooz.set(0, foo);

you're trying to assign the variable of some type to the variable of unknown type (since the function signature is like void set(int, ?)), which can never be possible, whatever the type of foo would be. In your case the type of foo is Object and you cannot assign it to a variable of some unknown type, which in fact may be Foo, Bar or any other.