为什么 javac 允许一些不可能的强制转换而不是其他的?

Mik*_*ski 53 java casting compiler-errors javac

如果我尝试将 a 转换String为 a java.util.Date,Java 编译器会捕获错误。那么为什么编译器不将以下内容标记为错误呢?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;
Run Code Online (Sandbox Code Playgroud)

当然,JVMClassCastException在运行时抛出 a ,但编译器不会标记它。

行为与 javac 1.8.0_212 和 11.0.2 相同。

Zab*_*uza 87

演员阵容技术上可行的。javac 无法轻易证明您的情况并非如此,并且 JLS 实际上将其定义为有效的 Java 程序,因此标记错误将是不正确的。

这是因为List是一个接口。因此,您可以拥有一个Date实际实现List伪装为List此处的a 的子类-然后将其强制转换Date为完全可以。例如:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}
Run Code Online (Sandbox Code Playgroud)

进而:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine
Run Code Online (Sandbox Code Playgroud)

检测这样的场景可能并不总是可能的,因为如果实例来自例如方法,则需要运行时信息。即使这样,编译器也需要付出更多的努力。由于类树根本无法匹配,编译器只会阻止绝对不可能的强制转换。正如所见,情况并非如此。

请注意,JLS 要求您的代码是有效的 Java 程序。在5.1.6.1 中。Allowed Narrowing Reference Conversion它说:

如果以下所有条件都真,则存在从引用类型S到引用类型的缩小引用转换:T

  • [...]
  • 一个有下列情形适用
    • [...]
    • S是接口类型,T是类类型,T不命名final类。

因此,即使编译器可以确定您的情况实际上是不可能的,也不允许标记错误,因为 JLS 将其定义为有效的 Java 程序。

只允许显示警告。

  • 值得注意的是,它捕获 String 的情况的原因是 String 是最终的,因此编译器知道没有类可以扩展它。 (16认同)
  • 实际上,我不认为 String 的“最终性”导致 `myDate = (Date) myString` 失败。使用 JLS 术语,该语句尝试从“S”(“字符串”)转换为“T”(“日期”)。这里,“S”不是接口类型,因此上面引用的 JLS 条件不适用。例如,尝试将 Calendar 转换为 Date,即使这两个类都不是最终类,您也会收到编译器错误。 (5认同)
  • 不禁止编译器进行检查。但禁止称其为错误。这将使编译器不合规。(看我的回答...) (3认同)
  • 添加一点行话,编译器需要证明类型“Date &amp; List”是“无人居住”的,但不足以证明它当前是“无人居住”的(将来可能是)。 (3认同)

Ste*_*n C 16

让我们考虑对您的示例进行概括:

List<String> strList = someMethod();       
Date d = (Date) strList;
Run Code Online (Sandbox Code Playgroud)

这些Date d = (Date) strList;是不是编译错误的主要原因。

  • 直观的原因是,编译器不(通常)知道精确的类型由该方法调用所返回的对象的。有可能除了是实现 的类之外List,它也是的子类Date

  • 技术的原因是,Java语言规范“允许”的狭窄基准转换对应于这种类型的演员。根据JLS 5.1.6.1

    “如果满足以下所有条件,则存在从引用类型S到引用类型的缩小引用转换T:”

    ...

    5) "S是接口类型,T是类类型,T不命名final类。"

    ...

    在另一个地方,JLS 还说在运行时可能会抛出异常......

    请注意,JLS 5.1.6.1 的确定基于所涉及变量的声明类型,而不是实际的运行时类型。在一般情况下,编译器不知道也不可能知道实际的运行时类型。


那么,为什么 Java 编译器不能确定强制转换不起作用呢?

  • 在我的示例中,someMethod调用可以返回多种类型的对象。即使编译器能够分析方法体并确定可以返回的精确类型集,也没有什么可以阻止有人更改它以返回不同类型......在编译调用它的代码之后。这就是 JLS 5.1.6.1 所说的内容的基本原因。

  • 在您的示例中,智能编译器可以确定强制转换永远不会成功。并且允许发出编译时警告来指出问题。

那么为什么不允许智能编译器说这是一个错误呢?

  • 因为 JLS 说这是一个有效的程序。时期。任何将此称为错误的编译器都不符合 Java 标准。

  • 此外,任何拒绝 JLS 和其他编译器认为有效的Java 程序的编译器都会阻碍 Java 源代码的可移植性。

  • 支持以下事实:*编译调用类后,被调用函数的实现可能会改变*,因此即使在编译时可以证明,使用被调用者的当前实现,强制转换是不可能的,但情况可能并非如此*当被调用者更改或被替换时,稍后运行时间*。 (4认同)
  • 支持强调如果编译器试图变得太聪明就会引入的可移植性问题。 (2认同)