【发布时间】:2021-05-10 12:17:25
【问题描述】:
(这个问题的灵感来自this question,我答错了。)
此代码无法编译:
List<? extends List<Number>> list = new ArrayList<>();
List<List<Double>> anotherList = (List<List<Double>>) list;
请注意,IntelliJ 不会报告任何错误。只有当我点击“运行”时它才会编译失败。
我明白为什么这不能在概念层面上编译。 list 是“扩展 List<Number> 的东西”的列表,而“某物”永远不可能是 List<Double>,因为 List<Double> 不是 List<Number> 的子类型,因为没有任何类型可以同时实现这两者,因为它们有相同的擦除。
但是,当我尝试按照语言规范中的措辞来确定这个转换是否有效时,我发现语言规范似乎说这是一个有效的转换!
这是我的推理:
演员表满足了从S (List<? extends List<Number>>) 到T (List<List<Double>>) 的缩小引用转换的所有三个要求。
5.1.6.1。允许的缩小参考转换
存在从引用类型 S 到 如果满足以下所有条件,则引用类型 T:
S 不是 T 的子类型
如果存在作为 T 的超类型的参数化类型 X,以及作为 S 的超类型的参数化类型 Y,则 X 和 Y 的擦除相同,则 X 和 Y 不可证明 不同的(第 4.5 节)。
以下情况之一适用:
- S 和 T 是接口类型。
- [...]
第一点和第三点是微不足道的。为了证明第二点是正确的,我们将Collection<List<Double>> 设为List<List<Double>> 的参数化超类型,将Collection<? extends List<Number>> 设为List<? extends List<Number>> 的参数化超类型。它们都擦除为相同的类型Collection。现在我们需要证明Collection<? extends List<Number>> 和Collection<List<Double>> 不是provably distinct (§4.5)。同样的论点也适用于Iterable<...>。
编辑:我刚刚意识到List<List<Double>> 的超类型还包括List<? extends List<Double>> 之类的东西,而不仅仅是List 的超接口。但我认为这不会使这个论点无效,因为关键是 1. 在 X 和 Y 中至少有一个通配符 2. X 和 Y 的通配符边界/类型参数是彼此的子类型。
两个参数化类型可以证明是不同的,如果其中任何一个 以下是正确的:
它们是不同泛型类型声明的参数化。
它们的任何类型参数都可以证明是不同的。
显然,由于它们都擦除为相同的类型,因此第一个条件不可能为真。我们只需要证明第二个条件为假即可。
在§4.5.1 中,规范定义了“类型参数可证明是不同的”:
如果满足以下条件之一,则两个类型参数可证明是不同的 真的:
- [...]
- 一个类型参数是一个类型变量或通配符,其上限(来自捕获转换(第 5.1.10 节),如有必要)为 S;和 其他类型参数 T 不是类型变量或通配符;也没有 |S| <:>
(为简洁起见,未显示其他(通常为错误的)条件)这里,S 是 List<Number>,T 是 List<Double>。两者|S| <:>direct 超类型关系的自反和传递闭包)。
因此Collection<? extends List<Number>> 和Collection<List<Double>> 的类型参数不可证明是不同的,因此List<? extends List<Number>> 和List<List<Double>> 不可证明是不同的,因此存在(或应该)从List<? extends List<Number>> 到List<List<Double>> 的转换!
我的推理错误在哪里?我错过了规范的其他部分吗?
【问题讨论】:
-
List<? extends List<Number>>表示,它可以是List<ArrayList<Number>>或List<LinkedList<Number>>。但不是List<List<Double>>。 -
我hate 缩小了关于有界通配符的捕获转换。这可能是我一生中第四次遇到类似问题。具体来说,来自捕获转换(第 5.1.10 节),如有必要。 如果需要是什么意思。捕获转换是否始终应用?当然还有“上限……”。
JLS没有解释(至少对我来说),那是什么。我也很想知道答案:( -
或者甚至阅读这个issue 说:“否则,将通配符和类型变量映射到它们的上限,然后测试它们的擦除是否是相关的类或接口(即,一种擦除类型是另一个的子类型)”,只是紧接着说:这是不健全的......。对我来说,这是一个
JLS错误,但我不敢发布作为答案。 -
@Eugene 让我想起了 this answer 的后半部分,这是关于作业(没有演员表),但仍然是类似(甚至相同)的不健全。
标签: java language-lawyer