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

2022-08-31 19:47:03

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

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

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

javac 1.8.0_212 和 11.0.2 的行为相同。


答案 1

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

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

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 中。允许缩小参考转换,它说:

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

  • [...]
  • 以下情况之一适用
    • [...]
    • S是一种接口类型,是一种类类型,并且不命名类。TTfinal

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

它只允许显示警告。


答案 2

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

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

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

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

  • 技术原因是 Java 语言规范“允许”与此类型强制转换相对应的缩小引用转换。根据 JLS 5.1.6.1

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

    ...

    5)S是接口类型,T是类类型,T不命名最终类。

    ...

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

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


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

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

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

那么,为什么聪明的编译器不允许说这是一个错误呢?

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

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


推荐