如果我尝试将String
强制转换为java.util.Date
,则Java编译器会捕获该错误。那么,为什么编译器不将以下内容标记为错误?
List<String> strList = new ArrayList<>();
Date d = (Date) strList;
当然,JVM在运行时会抛出ClassCastException
,但编译器不会对其进行标记。
行为与javac 1.8.0_212和11.0.2相同。
答案 0 :(得分:87)
演员 在技术上是可能的。 javac不能轻易证明您的情况并非如此,而JLS实际上将其定义为有效的Java程序,因此标记错误将是不正确的。
这是因为List
是一个接口。因此,您可以拥有一个Date
的子类,该子类实际上实现了在这里伪装成List
的{{1}}-然后将其强制转换为List
是可以的。例如:
Date
然后:
public class SneakyListDate extends Date implements List<Foo> {
...
}
不一定总是可以检测到这种情况,因为如果实例来自例如某个方法,则需要运行时信息。即使这样,编译器也需要付出更多的努力。编译器仅阻止由于类树根本无法匹配而绝对不可能进行的强制类型转换。如前所述,这里不是这种情况。
请注意,JLS要求您的代码是有效的Java程序。在5.1.6.1. Allowed Narrowing Reference Conversion中说:
如果以下所有全部为 true ,则存在从引用类型
List<Foo> list = new SneakyListDate(); Date date = (Date) list; // This one is valid, compiles and runs just fine
到引用类型S
的缩小引用转换:
- [...]
- 以下一种情况适用:
- [...]
T
是接口类型,S
是类类型,而T
没有命名T
类。
因此,即使编译器可以弄清您的情况实际上是不可能的,也不允许标记错误,因为JLS将其定义为有效的Java程序。
只允许显示警告。
答案 1 :(得分:16)
让我们考虑一下您的示例的概括:
List<String> strList = someMethod();
Date d = (Date) strList;
这是Date d = (Date) strList;
不是编译错误的主要原因。
直观原因是(通常)编译器不知道该方法调用返回的对象的确切类型。除了作为实现List
的类之外,也是 Date
的子类。
技术原因是因为Java语言规范“允许”与该类型转换相对应的缩小引用转换。根据{{3}}:
“如果满足以下所有条件,则存在从引用类型
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源代码的可移植性。
答案 2 :(得分:2)
5.5.1. Reference Type Casting:
给出编译时引用类型
S
(源)和编译时 引用类型T
(目标),存在从S
到的转换转换T
,如果由于以下规则而没有发生编译时错误。[...]
如果
S
是接口类型:
[...]
如果
T
不是最终的类或接口类型,则如果存在X
的超类型T
和的超类型Y
S
,使得X
和Y
都被证明是截然不同的参数化 类型,并且X
和Y
的擦除是相同的, 发生编译时错误。否则,强制转换在编译时始终合法(因为即使
T
未实现S
,也可能是T
的子类)。 < / p>
List<String>
是S
,而Date
是T
。