无法使用通配符编译相关的Java泛型参数

7
以下是一段简短的Java示例,由于原因不明,它无法编译:
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);
    }
}

ctx.put这一行会产生以下错误:

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

如果不使用通配符,属性模式可以正常工作。

为什么编译器不接受带通配符类型的值?


1
这可能是类型推断系统的限制。尝试使用以下代码:ctx.<List<?>, ListAttribute>put(ListAttribute.class, list) - Bhesh Gurung
1
你的问题表述得非常好!SSCCE,错误信息等。 - erickson
1
上周相关问题:https://dev59.com/4WQn5IYBdhLWcg3w7a2K#16451216 - Mark Peters
2个回答

4
问题在于,list的参数类型实际上不是List<?>。编译器首先进行“通配符捕获”,将其类型转换为List<x>(x为某个类型)。通常这样更具信息性和帮助性。但在您的情况下不是这样。它会让类型推断认为T=List<x>,但ListAttribute并没有扩展Attribute<List<x>>
您可以提供显式的类型参数来解决这个问题。
ctx.<List<?>, ListAttribute>put(ListAttribute.class, list);
      (T)      (A)

这个解决方案可以满足编译器,但不能满足最初创建的模式,以避免冗长的提示,如类型转换和显式类型参数。 - iterator
@迭代器 - 您的原始代码现在使用不同的推断规则在Java8中编译。然而,它有可能在Java7中也应该正常工作,但是javac存在一个bug。不确定。 - ZhongYu

3
替换
public <T, A extends Attribute<T>>

带有

public <T, A extends Attribute<? super T>>

考虑到我们不知道put的实现方式,你怎么能假设这是可以的呢? - Paul Bellora
那么它是类型安全的吗?因为在我看来,这似乎意味着我可以将超类型层次结构中的所有实例作为值放入其中,而这不是我想允许的。 - iterator
2
我认为我们可以假设这是可以的,因为您必须记住value可能已经是T的任何子类。因此,如果valueString,那么A可以是Attribute<String>Attribute<CharSequence>Attribute<Object>。这唯一防止的是从A生成一个T,这似乎是合理的。 - Mark Peters
经过一段时间的思考,这听起来是合理的。所以那真的是解决方案,并且它是类型安全的,因为属性类型可以在类型层次结构中处于较高位置。现在它运行得非常好。 - iterator
@MarkPeters - 没问题,但这并不是必要的;在类型推断期间,T足够自由,因此Attribute<T>应该可以工作。顺便说一句,OP的代码可以在Java8中编译。 - ZhongYu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接