为什么编译器不推断泛型中的捕获关系?
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
,但可以用通配符引用。因此,在类型擦除之后,您的程序无法确定这两个通配符是指兼容的实体;除非你明确告诉它这么多。
想象一下这样的界面
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
,但可以用通配符引用。因此,在类型擦除之后,您的程序无法确定这两个通配符是指兼容的实体;除非你明确告诉它这么多。