7

The following little Java example won't compile for unclear reasoning:

package genericsissue;

import java.util.ArrayList;
import java.util.List;

interface Attribute<V> {}

interface ListAttribute extends Attribute<List<?>> {}

public class Context {
    public <T, A extends Attribute<T>> void put(Class<A> attribute, T value) {
        // implementation does not matter for the issue
    }

    public static void main(String[] args) {
        Context ctx = new Context();
        List<?> list = new ArrayList<String>();
        ctx.put(ListAttribute.class, list);
    }
}

The line with ctx.put produces following error:

Context.java:18: <T,A>put(java.lang.Class<A>,T) in genericsissue.Context cannot be applied to (java.lang.Class<genericsissue.ListAttribute>,java.util.List<capture#35 of ?>)

If working without wildcards the attribute pattern works fine.

Is there any explanation why the compiler does not accept the value with wildcard typing?

iterator
  • 280
  • 1
  • 7

2 Answers2

4

The problem is, the argument type of list is not really List<?>. Compiler does a "wildcard capture" first to convert its type to List<x> for some x. Usually this is more informative and helpful. But not in your case. It drives type inference to think that T=List<x>, but ListAttribute does not extend Attribute<List<x>>

You can provide explicit type arguments to work around it

ctx.<List<?>, ListAttribute>put(ListAttribute.class, list);
      (T)      (A)
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • This solution satisfies the compiler but not the pattern which was initially created to avoid verbose hints like type casts and explicit type argument. – iterator May 17 '13 at 19:47
  • @iterator - your original code now compiles in java8 with different inference rules. however, it's possible that it should have worked in java7 too but there was a bug in javac. Not sure. – ZhongYu Jul 27 '15 at 23:49
3

Replace

public <T, A extends Attribute<T>>

With

public <T, A extends Attribute<? super T>>
Arnaud Denoyelle
  • 29,980
  • 16
  • 92
  • 148
  • How can you assume this is okay, given we don't know the implementation of `put`? – Paul Bellora May 17 '13 at 18:05
  • Is it then typesafe? Because to me it looks then like I can put all instances from the super type hierarchy as values which is not what I want to allow. – iterator May 17 '13 at 19:48
  • 2
    I think we can assume this is OK because you have to keep in mind that `value` could be any subclass of `T` already. So if `value` is a `String`, `A` could be `Attribute`, `Attribute`, or `Attribute` as it stands now. The only thing this will prevent is *producing* a `T` from the `A`, and that seems reasonable. – Mark Peters May 17 '13 at 20:19
  • Sounds reasonable after thinking for a while. So that really is the solution and it IS typesafe because the Attribute type can stand higher in the type hierarchy. It works like a charm now. – iterator May 17 '13 at 21:04
  • @MarkPeters - it's OK, but it shouldn't be necessary; `T` is free enough during inference, so `Attribute` should work. BTW, OP's code compiles in Java8. – ZhongYu Jul 27 '15 at 23:49