【问题标题】:Why does javac allow some impossible casts and not others?为什么 javac 允许一些不可能的强制转换而不是其他强制转换?
【发布时间】:2020-07-02 18:45:57
【问题描述】:

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

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

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

行为与 javac 1.8.0_212 和 11.0.2 相同。

【问题讨论】:

  • List 没有什么特别之处。 Date d = (Date) new Object();
  • 我最近一直在玩 arduino。我会喜欢一个编译器,它不会愉快地接受任何转换,然后只是以完全不可预测的结果执行它们。字符串转整数?确定的事!双倍到整数?是的先生!字符串到布尔值?至少那一个大多是错误的......
  • @ElliottFrisch:Date 和 Object 之间有明显的继承关系,但 Date 和 List 之间没有关系。所以我希望编译器标记这个转换,就像它标记从 String 到 Date 的转换一样。但正如 Zabuza 在他们出色的回答中所解释的那样,List 是一个接口,因此如果 strList 是实现 List 的类的实例,则强制转换是合法的。
  • 这是一个经常出现的问题,我确信我已经看到了多个重复的问题。它基本上是强相关的反向版本:stackoverflow.com/questions/21812289/…
  • @StianYttervik -fpermissive 就是这样做的。打开编译器警告。

标签: java casting compiler-errors javac


【解决方案1】:

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

这是因为List 是一个接口。所以你可以有一个Date 的子类,它实际上实现了List,在这里伪装成List——然后将它转换为Date 就可以了。例如:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

然后:

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

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

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

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

  • [...]
  • 一种以下情况适用
    • [...]
    • S 是接口类型,T 是类类型,T 没有命名 final 类。

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

只允许显示警告。

【讨论】:

  • 值得注意的是,它捕捉到String的原因是String是final的,所以编译器知道没有类可以扩展它。
  • 实际上,我不认为是 String 的“最终性”导致myDate = (Date) myString 失败。使用 JLS 术语,该语句尝试将 SString)转换为 TDate)。这里,S 不是接口类型,所以上面引用的 JLS 条件不适用。例如,尝试将 Calendar 强制转换为 Date,即使两个类都不是 final,您也会收到编译器错误。
  • 我不知道是否会失望编译器无法进行足够的静态分析来证明 strList 只能是 ArrayList 类型。
  • 不禁止编译器检查。但禁止称其为错误。这将使编译器不兼容。 (看我的回答……)
  • 添加一点行话,编译器需要证明Date &amp; List类型是uninhabitable,仅仅证明它是是不够的目前无人居住(未来可能)。
【解决方案2】:

让我们考虑一下您的示例的概括:

List<String> strList = someMethod();       
Date d = (Date) strList;

这些是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 说这是一个有效的程序。时期。任何将其称为 error 的编译器都不符合 Java。

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

【讨论】:

  • 赞成在编译调用类后,被调用函数的实现可能会改变,所以即使它在编译时是可证明的,对于被调用者的当前实现,演员是不可能的,当被调用者已更改或被替换时,在以后的运行时间可能不会如此。
  • 赞成突出显示如果编译器过于智能会引入的可移植性问题。
【解决方案3】:

5.5.1. Reference Type Casting:

给定一个编译时引用类型S(源代码)和一个编译时 引用类型T(目标),存在从S到的转换转换 T 如果没有由于以下规则而发生编译时错误。

[...]

如果S是一个接口类型:

  • [...]

  • 如果T 是非final 的类或接口类型,那么如果存在T 的超类型X,以及Y 的超类型 S,这样XY 可以证明是不同的参数化 类型,并且 XY 的擦除是相同的,a 发生编译时错误。

    否则,转换在编译时总是合法的(因为即使T 没有实现ST 的子类也可能)。

List&lt;String&gt;SDateT 在你的情况下。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-04
    • 2019-03-12
    • 1970-01-01
    • 2015-09-03
    • 2019-06-09
    相关资源
    最近更新 更多