为什么编译器不推断泛型中的捕获关系?

Why does the compiler not deduce captures relationships in generics?

想象一下这样的界面

public interface MessageParameter<T> {

  public List<T> unmarshal(byte[] array);
  public int getLength(List<T> values);

}

以及该接口的消费者

public class GenericUser {

  List<MessageParameter<?>> payload = new ArrayList<>();

  public void run() {
     byte[] byteArray = new byte[] { 1, 2 };

     for (MessageParameter<?> element : payload) {
        element.getLength(element.unmarshal(byteArray)); //compiler error
     }
  }
}

编译报错

 The method getLength(List<capture#1-of ?>) in the type MessageParameter<capture#1-of ?> is not applicable for the arguments (List<capture#2-of ?>)

很明显,因为我在两个方法调用中都使用了 element,所以两者的类型是相同的,应该被允许。另一种问同样问题的方法,为什么编译器创建 capture#2?为什么不能推断出它们在逻辑上是相同的 capture?

我错过了什么吗?是否有此代码会抛出运行时异常的反例?

我的主要问题不是如何修复代码(尽管这也很有趣,我目前的解决方案是使用 Object 而不是 ?),而是 这个错误的逻辑原因是什么? 在我看来,这更像是编译器实现上的缺陷,而不是逻辑限制

答案是编译器没有那么聪明地接受?对应的运行时类型是相同的,因为它不关心你的单行表达式涉及相同的element :

element.getLength(element.unmarshal(byteArray));

在语义上类似于:

List<?> unmarshalledList = element.unmarshal(byteArray);
element.getLength(unmarshalledList);

在这种情况下,列表 unmarshalledList 肯定必须具有与 getLength() 预期的列表相同的 "any-type" 并不那么明显。以上是两个单独的语句(即使它们是连续的)。想象一下,它们不是连续的。你可能有这样的东西:

MessageParameter<?> otherElement = getOtherElement();
for (MessageParameter<?> element : payload) {
    List<?> unmarshalledList = element.unmarshal(byteArray);
    // unmarshalledList can be re-assigned from another parameterized type
    unmarshalledList = otherElement.unmarshal(byteArray);
    element.getLength(unmarshalledList);  // error
}

换句话说,当程序到达调用 getLength 的语句时,编译器不能假定变量 unmarshalledList 将保留与 element 相同的 ? 类型相同 元素。它可以重新分配给介于两者之间的不同参数化类型。

我认为您误解了 ? 的一般含义。该符号称为通配符;它指的是一个真正未知的类型。这与您当前的工作不同,在您当前的工作中使用 Object 确实会更好,因为您的类型并非完全未知 — 您知道它们都实现了 Object 并且可以这样引用它们。 (? extends Object 在某些地方可能会更好)。

至于为什么?不是Object的同义词,请记住基元不继承自Object,但可以用通配符引用。因此,在类型擦除之后,您的程序无法确定这两个通配符是指兼容的实体;除非你明确告诉它这么多。