为什么Javac允许某些不可能的转换而不允许其他转换?


52

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

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

当然,JVM ClassCastException在运行时会抛出a ,但是编译器不会对其进行标记。

该行为与javac 1.8.0_212和11.0.2相同。


2
List这里没什么特别的。Date d = (Date) new Object();
Elliott Frisch

1
我最近一直在玩arduino。我会喜欢一个不快乐地接受任何类型转换的编译器,然后以完全不可预测的结果完成它们。字符串到整数?当然可以!从双到整数?是的先生!字符串到布尔值?至少大部分是假的...
Stian Yttervik

@ElliottFrisch:日期和对象之间有明显的继承关系,但日期和列表之间没有关系。因此,我希望编译器标记此转换,就像标记从String到Date的转换一样。但是正如Zabuza在其出色的回答中所解释的那样,List是一个接口,因此,如果strList是实现List的类的实例,则强制转换是合法的。
Mike Woinoski

这是一个经常重复出现的问题,我敢肯定我已经看到过多次重复的问题。它基本上是密切相关的反向版本:stackoverflow.com/questions/21812289/...
绿巨人

1
@StianYttervik -fpermissive在做什么。打开编译器警告。
bobsburner

Answers:


86

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

这是因为List是一个接口。因此,您可以拥有一个Date实际上List伪装成List此处的a的子类-然后将其强制转换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中。允许缩小参考转换说:

变窄引用转换从引用类型存在S于引用类型T,如果所有以下的是

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

所以,即使编译器能够弄清楚,你的情况实际上是可证明是不可能的,因为JLS把它定义为有效的Java程序,不允许以标记错误。

仅允许显示警告。


16
值得注意的是,使用String捕获情况的原因是String是最终的,因此编译器知道没有类可以扩展它。
MTilsted

5
实际上,我认为不是String的“最终性”会myDate = (Date) myString导致失败。该语句使用JLS术语尝试将SString)转换为TDate)。在这里,S它不是接口类型,因此上面引用的JLS条件不适用。例如,尝试将Calendar强制转换为Date,即使两个类都不是final,您也会遇到编译器错误。
Mike Woinoski

1
我不知道是否让编译器失望,因为编译器无法进行足够的静态分析来证明strList只能是ArrayList类型。
约书亚

3
禁止编译器进行检查。但是禁止将其称为错误。这将使编译器不兼容。(请参阅我的回答...)
Stephen C

3
要增加一点措辞,编译器将需要证明该类型Date & List是不适合居住的,不足以证明它当前是不居住的(可能在将来)。
Polygnome

15

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

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

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


4
支持以下事实:在编译了调用类之后,被调用函数的实现可能会更改,因此,即使在编译时可以证明这种情况,对于当前的被调用方实现,强制转换也是不可能的,但在以后的运行时可能不会这样被呼叫者已更改或替换时。
彼得-恢复莫妮卡

2
赞扬突出了如果编译器试图变得太聪明会引入的可移植性问题。
Mike Woinoski

2

5.5.1。参考类型转换:

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

[...]

如果S是接口类型:

  • [...]

  • 如果T是,不是最后一个类或接口类型,那么如果存在超型XT,和超类型YS,使得两个XY是可证明不同参数化类型,那的擦除XY是相同的,一个编译时间错误发生。

    否则,强制转换在编译时始终是合法的(因为即使T未实现ST也可能是may的子类)。

List<String>SDateT你的情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.