Java转换:是编译器错了,还是语言规范错了,还是我错了?

2022-09-02 11:00:08

我一直在阅读Java语言规范,第3版,并发现我认为规范和javac编译器实现之间存在差异。Eclipse 编译器中也存在同样的差异。

15.16 节讨论了强制转换表达式。它说,如果参数类型无法通过强制转换转换为强制转换类型,则应该是编译时错误(第5.5节):

如果根据强制转换转换规则 (§5.5) 永远不能将操作数的编译时类型强制转换为强制转换运算符指定的类型,则这是一个编译时错误。否则,在运行时,通过强制转换转换为强制转换运算符指定的类型,操作数值将转换(如有必要)。

第5.5节讨论了铸造转换。它提供了允许的转换类型的列表。列表中特别缺少的是“拆箱转换,然后加宽/缩小基元转换”。然而,javac编译器(以及Eclipse编译器)似乎确实允许这种确切的转换顺序。例如:

long l = (long) Integer.valueOf(45);

...编译刚刚好。(有问题的强制转换是强制转换为;参数的类型为 ,因此转换需要取消装箱,然后进行加宽的基元转换)。longjava.lang.Integerint

同样,根据 JLS,不应该从 强制转换为 ,因为这(根据 5.1.4)需要加宽基元转换缩小基元转换 - 但是,编译器也允许这种强制转换。bytechar

任何人都可以启发我吗?

编辑:自从问这个问题以来,我已经向Oracle提交了错误报告。他们的回答是,这是“JLS中的一个小故障”。


答案 1

我认为你是对的,编译器是对的,规范是错误的。

这编译:这不会:(Object)45(Long)45

理解编译器行为(包括我正在使用的 Intellij)的唯一方法是修改转换转换以同意赋值转换和方法调用转换:

  • 装箱转换 (§5.1.7),然后可选择加宽参考转换

  • 取消装箱转换 (§5.1.8),然后选择性地进行加宽基元转换。

  • 加宽和缩小原始对流

该规范确实说过“强制转换比赋值或方法调用转换更具包容性:强制转换可以执行字符串转换或捕获转换以外的任何允许的转换”


答案 2

根据我的阅读,从到的强制转换是这个子句所允许的:intlong

如果基元类型相同,则可以通过标识转换将基元类型的值强制转换为另一个基元类型,也可以通过加宽基元转换或缩小基元转换将基元类型的值强制转换为另一个基元类型。

转换为是一种加宽基元转换intlong

这只剩下从 到 的转换,这在最后一个项目符号中得到了容纳:Integerint

拆箱转换

当然,在示例中甚至不需要强制转换。long

请考虑以下四个定义:

final Integer io = Integer.valueOf(45);
final int i = io;
final long l1 = (long)i;
final long l2 = i;

你认为他们中的任何一个令人惊讶吗?您的原始示例看起来没有任何不同;它只是省略了中间变量。


推荐