【问题标题】:Which part of the JLS specifies that you can't cast from List<? extends List<Superclass>> to List<List<Subclass>>?JLS 的哪一部分指定您不能从 List<?将 List<Superclass>> 扩展到 List<List<Subclass>>?
【发布时间】: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&lt;Number&gt; 的东西”的列表,而“某物”永远不可能是 List&lt;Double&gt;,因为 List&lt;Double&gt; 不是 List&lt;Number&gt; 的子类型,因为没有任何类型可以同时实现这两者,因为它们有相同的擦除。

但是,当我尝试按照语言规范中的措辞来确定这个转换是否有效时,我发现语言规范似乎说这是一个有效的转换!

这是我的推理:

演员表满足了从S (List&lt;? extends List&lt;Number&gt;&gt;) 到T (List&lt;List&lt;Double&gt;&gt;) 的缩小引用转换的所有三个要求。

5.1.6.1。允许的缩小参考转换

存在从引用类型 S 到 如果满足以下所有条件,则引用类型 T:

  • S 不是 T 的子类型

  • 如果存在作为 T 的超类型的参数化类型 X,以及作为 S 的超类型的参数化类型 Y,则 X 和 Y 的擦除相同,则 X 和 Y 不可证明 不同的(第 4.5 节)。

  • 以下情况之一适用:

    • S 和 T 是接口类型。
    • [...]

第一点和第三点是微不足道的。为了证明第二点是正确的,我们将Collection&lt;List&lt;Double&gt;&gt; 设为List&lt;List&lt;Double&gt;&gt; 的参数化超类型,将Collection&lt;? extends List&lt;Number&gt;&gt; 设为List&lt;? extends List&lt;Number&gt;&gt; 的参数化超类型。它们都擦除为相同的类型Collection。现在我们需要证明Collection&lt;? extends List&lt;Number&gt;&gt;Collection&lt;List&lt;Double&gt;&gt; 不是provably distinct (§4.5)。同样的论点也适用于Iterable&lt;...&gt;

编辑:我刚刚意识到List&lt;List&lt;Double&gt;&gt; 的超类型还包括List&lt;? extends List&lt;Double&gt;&gt; 之类的东西,而不仅仅是List 的超接口。但我认为这不会使这个论点无效,因为关键是 1. 在 XY 中至少有一个通配符 2. XY 的通配符边界/类型参数是彼此的子类型。

两个参数化类型可以证明是不同的,如果其中任何一个 以下是正确的:

  • 它们是不同泛型类型声明的参数化。

  • 它们的任何类型参数都可以证明是不同的。

显然,由于它们都擦除为相同的类型,因此第一个条件不可能为真。我们只需要证明第二个条件为假即可。

§4.5.1 中,规范定义了“类型参数可证明是不同的”:

如果满足以下条件之一,则两个类型参数可证明是不同的 真的:

  • [...]
  • 一个类型参数是一个类型变量或通配符,其上限(来自捕获转换(第 5.1.10 节),如有必要)为 S;和 其他类型参数 T 不是类型变量或通配符;也没有 |S| <:>

(为简洁起见,未显示其他(通常为错误的)条件)这里,SList&lt;Number&gt;TList&lt;Double&gt;。两者|S| <:>direct 超类型关系的自反和传递闭包)。

因此Collection&lt;? extends List&lt;Number&gt;&gt;Collection&lt;List&lt;Double&gt;&gt; 的类型参数不可证明是不同的,因此List&lt;? extends List&lt;Number&gt;&gt;List&lt;List&lt;Double&gt;&gt; 不可证明是不同的,因此存在(或应该)从List&lt;? extends List&lt;Number&gt;&gt;List&lt;List&lt;Double&gt;&gt; 的转换!

我的推理错误在哪里?我错过了规范的其他部分吗?

【问题讨论】:

  • List&lt;? extends List&lt;Number&gt;&gt; 表示,它可以是List&lt;ArrayList&lt;Number&gt;&gt;List&lt;LinkedList&lt;Number&gt;&gt;。但不是List&lt;List&lt;Double&gt;&gt;
  • hate 缩小了关于有界通配符的捕获转换。这可能是我一生中第四次遇到类似问题。具体来说,来自捕获转换(第 5.1.10 节),如有必要如果需要是什么意思。捕获转换是否始终应用?当然还有“上限……”。 JLS 没有解释(至少对我来说),那是什么。我也很想知道答案:(
  • 或者甚至阅读这个issue 说:“否则,将通配符和类型变量映射到它们的上限,然后测试它们的擦除是否是相关的类或接口(即,一种擦除类型是另一个的子类型)”,只是紧接着说:这是不健全的......。对我来说,这是一个JLS 错误,但我不敢发布作为答案。
  • @Eugene 让我想起了 this answer 的后半部分,这是关于作业(没有演员表),但仍然是类似(甚至相同)的不健全。

标签: java language-lawyer


【解决方案1】:

好吧,我会在Holger's confirmation and answer 之后说:(至少)在这个位置没有指定 JLS。有一些相关的 JDK 错误可以解决相同的想法,值得注意的 this one,直接通过以下方式解决您的问题:

.. 否则,将通配符和类型变量映射到它们的上界,然后测试它们的擦除是否是相关的类或接口(即一个擦除的类型是另一个的子类型)

只在下一句立即开始:

这是不合理的......

因此,该错误承认 JLS 需要围绕本章进行一些更正。

从你对 JLS 的引用中,我也一直在努力解决两点:

  • 一个类型参数是类型变量或通配符,具有上限(来自捕获转换(第 5.1.10 节),如有必要)...

    我知道 捕获转换 是什么,但我不知道它可能是可选的(通过“如果需要”)。我一直认为它一直在每个位置进行。

  • 什么是捕获的转换类型上限

    就你而言,上限List&lt;Number&gt; 还是List&lt;?&gt;,例如?以我的理解(或缺乏精确的 JLS 解释),这两种方式都可以理解。

所有这些(+ 你对 JLS 的精彩评论)让我怀疑这里 JLS 的正确性,特别是因为 javac 没有遵循这些完全相同的规则。

【讨论】:

  • 捕获转换不会发生在instanceof 以及作业的左侧(否则,您无法进行List&lt;?&gt; list = expression;)。
  • @Holger 在这里让我有些困惑。这是针对不同问答的评论吗?或者,如果在这里,我可以详细了解您的意思吗?
  • 对“我一直认为它一直在每个位置执行”的响应。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-15
  • 2014-08-14
  • 1970-01-01
  • 2019-12-09
  • 2021-11-26
  • 2022-12-17
相关资源
最近更新 更多