将超类型的数组强制转换为子类型

3
这为什么在Java中会导致运行时异常而不是编译时错误呢?
Object[] objects = new Object[10];
String[] strings = (String[])objects;
3个回答

5

必须在运行时进行检查,因为存在以下情况:

public class Test {
  public static void main(String[] args){
    String[] stringsBase = {"aaa", "bbb", "ccc"};
    Object[] objects = stringsBase;
    String[] strings = (String[])objects;
    System.out.println(strings[1]);
  }
}

这是一个有效、可行的程序。如果没有进行流分析,编译器就不知道对象引用的是一个被创建为Object[]的数组,还是像这里一样被创建为String[]。


而且,为什么编译器不能处理这样的情况呢? - Matt Ball
哎呀?编译器处理得很好啊。 - Patricia Shanahan
澄清一下,我的意思是:为什么编译器不能聪明地区分强制转换String[]→Object[]→String[]和例如String[]→Object[]→Integer[]?这是一个引导性问题。当然,答案是JLS不要求编译器执行此类检查。 - Matt Ball
JLS这样说只是把问题推回了一个层次。它仍然留下了为什么JLS这样说的问题,这就是我试图回答的问题。总的来说,无论我们是否处理有效的转换,都是不可判定的。我想JLS可能要求类似于“明确分配”系统的东西,但假设程序员编写的转换是正确的,并且测试应该留给运行时,这更简单、更清晰,除非有强有力的证据表明相反。 - Patricia Shanahan
2
@MattBall - JLS并不要求实现这样做,因为在许多情况下这是不可能的(例如,如果在第一次转换后,该值被用作方法的参数,该方法会反转转换)。 - Periata Breatta

2
因为这是语言规范定义的编译时行为。简而言之,一个 Object[] 可以被强制转换为 String[] 而不会生成编译时错误,因为一个 Object 可以被强制转换为 String 而不会生成编译时错误。
长答案就是我引用了JLS的内容。来自Java语言规范 § 5.5.1. 引用类型转换:

给定编译时引用类型S(源)和编译时引用类型T(目标),如果遵循以下规则没有编译时错误,则从S到T存在强制类型转换。
...
如果S是数组类型SC[],即类型为SC的组件的数组:
...

  • 如果T是数组类型TC[],即类型为TC的组件的数组,则除非以下情况之一成立,否则会发生编译时错误:
    • TC和SC是相同的基本类型。
    • TC和SC都是引用类型,并且类型SC可以进行强制类型转换以变成TC。
在该部分有关强制类型转换的前面规则中,Object可以进行强制类型转换为String
如果S是一个类类型:
- 如果T也是一个类类型,那么要么|S| <: |T|,要么|T| <: |S|。否则,会在编译时出现错误。
只是以防你想知道:
- 如果符合§5.5.1.中的规则,引用类型的表达式可以进行强制转换为另一个引用类型,而不会出现编译时错误。
请注意,JLS使用|T|来表示类型T的擦除,同时S :> T表示S和T之间存在超类型关系。 因此,“|S|<: |T|或|T|<: |S|”可以理解为“S的擦除是T的子类型、相同类型或超类型”。

只是出于好奇...关于我可以编译这个的事实:StringBuilder sb = new StringBuilder(); Object o = sb; Integer i = (Integer) o; 你能解释一下为什么吗?这似乎表明上面的情况不是一个特例...但我肯定是理解错了什么。 - jahroy
@jahroy的回答已经被大幅编辑,但第二个引用涵盖了这一点。 - Matt Ball
嗯...希望我知道那个符号。但是我真的不明白它怎么应用...我在我的小例子中并没有尝试将其转换为字符串。我试图将一个StringBuilder转换为整数(这在运行时不起作用,但可以编译)。如果你不想回答也没关系...只是好奇。 - jahroy
@jahroy 刚刚添加了符号说明的解释,因为我自己一开始也没看懂。JLS并不要求编译器那么聪明。在您的中间转换为 Object 会让编译器感到困惑。如果您真的想理解它,请花时间仔细阅读JLS §5.5.1和5.5.2。 - Matt Ball
这个回答引出了一个问题。它实际上可以应用于关于Java行为的任何问题:“为什么当我尝试取消引用包含null的变量时,Java会抛出NullPointerException?因为JLS规定必须这样做。”归根结底,关于Java为什么执行某些由JLS指定的操作的问题应该被解释为为什么JLS以特定的方式编写,否则就没有意义。 - Jules

0

强制类型转换是Java提供的一项基本不安全的功能,旨在提高灵活性。如果编译器设置正确,则该代码会生成“未经检查”的警告。


3
这个回答有些含糊不清。 - Matt Ball

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接