Failing to compile correlated Java Generics parameters with wildcards
Asked Answered
C

2

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?

Cyanamide answered 17/5, 2013 at 15:47 Comment(3)
It's probably a limitation of the type inference system. Try: ctx.<List<?>, ListAttribute>put(ListAttribute.class, list).Zincography
Excellent formulation of your question! SSCCE, error message, etc.Deodar
Related question from last week: #16450299Girth
K
3

Replace

public <T, A extends Attribute<T>>

With

public <T, A extends Attribute<? super T>>
Kristof answered 17/5, 2013 at 15:54 Comment(5)
How can you assume this is okay, given we don't know the implementation of put?Ferous
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.Cyanamide
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<String>, Attribute<CharSequence>, or Attribute<Object> as it stands now. The only thing this will prevent is producing a T from the A, and that seems reasonable.Girth
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.Cyanamide
@MarkPeters - it's OK, but it shouldn't be necessary; T is free enough during inference, so Attribute<T> should work. BTW, OP's code compiles in Java8.Sandell
S
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)
Sandell answered 17/5, 2013 at 16:2 Comment(2)
This solution satisfies the compiler but not the pattern which was initially created to avoid verbose hints like type casts and explicit type argument.Cyanamide
@Cyanamide - 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.Sandell
K
3

Replace

public <T, A extends Attribute<T>>

With

public <T, A extends Attribute<? super T>>
Kristof answered 17/5, 2013 at 15:54 Comment(5)
How can you assume this is okay, given we don't know the implementation of put?Ferous
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.Cyanamide
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<String>, Attribute<CharSequence>, or Attribute<Object> as it stands now. The only thing this will prevent is producing a T from the A, and that seems reasonable.Girth
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.Cyanamide
@MarkPeters - it's OK, but it shouldn't be necessary; T is free enough during inference, so Attribute<T> should work. BTW, OP's code compiles in Java8.Sandell

© 2022 - 2024 — McMap. All rights reserved.