为什么 ArrayStoreException 是 RuntimeException?

Why is the ArrayStoreException a RuntimeException?

假设我们有以下程序:

class Fruit {}

class Apple extends Fruit {} 

class Jonathan extends Apple {} 

class Orange extends Fruit {} 

public class Main { 
    public static void main(String[] args) { 
        Fruit[] fruit = new Apple[10];

        try { 
            fruit[0] = new Fruit(); // ArrayStoreException 
            fruit[0] = new Orange(); // ArrayStoreException 
        } catch(Exception e) { System.out.println(e); } 
    } 
}

基于Java documentation

Thrown to indicate that an attempt has been made to store the wrong type of object into an array of objects.

我读过 here

When array is created it remembers what type of data it is meant to store.

如果数组记住它包含的数据类型,则意味着它知道它包含的数据类型。但是我发布的片段是正确编译的,所以在编译时数组显然不知道包含什么类型。

我的问题是:

  1. 为什么 ArrayStoreException 只在运行时抛出?

  2. 编译器缺少哪些信息来意识到该赋值是不可能的?

  3. 有没有这样的代码是正确的,所以没有抛出 ArrayStoreException

If the array remembers what type of data it contains, it means that it KNEW the type of data it contains.

在执行时,是的......就像在执行时一样,对象的类型是已知的:

Object x = "foo";
// The compiler won't let you call x.length() here, because the variable
// x is of type Object, not String.

另一种方法是让很多数组赋值隐式抛出一个已检查的异常。那太糟糕了 - 类似于检查 NullPointerException

What information are missing to the compiler to realise that that assignment is not possible?

如您所见,数组是协变的。当编译器看到对 AppleFruit[] 的赋值时,它不知道该数组的 actual 类型是什么。如果是 Fruit[]Apple[],那很好。如果是 Orange[] 则不是。该信息仅在执行时出现 - 同样,就像编译器不知道表达式是否绝对不为空一样。

Is there any cases in which such code is correct so no ArrayStoreException is thrown?

好吧,如果你有一个带有最终 class 编译时元素的数组,那么你就不能有任何更低的方差。例如:

public void foo(String[] array) {
    array[0] = "x";
}

由于 arraynull 或为空,它可能会抛出异常,但它永远不会抛出 ArrayStoreException,因为 String 是最终的。实现永远不可能是 SubclassOfString[].

这是一个运行时异常,原因与 ClassCastException 相同。在编译时并不总是可以判断类型是否符合您的预期。

考虑这个例子:

void method1() {
    Fruit[] fruits = getFruits();
    fruits[0] = new Orange();
}

Fruit[] getFruits() {
    if (someCondition) {
        return new Apple[5];
    } else {
        return new Orange[5];
    }
}

当您调用 getFruits() 时,编译器无法知道 someCondition 将处于什么状态。因此运行时异常。

在你的例子中,apple 和 orange 被隐式地转换为 fruit,因为它们是 fruit 的子类。这就是它不抛出异常的原因,这种行为是 OOP 的基础之一:它被称为多态性。 如果该数组被声明为苹果数组并且您尝试在其中添加水果(与您的情况相反),则会抛出异常:因为您只能隐式地从子对象转换为父对象(从父对象到子对象的转换应该是明确)。

When array is created it remembers what type of data it is meant to store.

数组"remembers"仅显示它在运行时实际包含的类型。

首先声明数组,在本例中为 Fruit 数组。

然后创建数组,在本例中为 Apple 的数组。

创建是在运行时进行的,但编译器的设计只是为了验证数组是否只被分配了它声明为的类型的对象。在运行时可能会发生很多事情。

考虑以下代码:

class Fruit {}

class Apple extends Fruit {} 

class Jonathan extends Apple {} 

class Orange extends Fruit {} 

public class Main { 
    public static void main(String[] args) { 
        Fruit[] fruit = new Apple[10];
        boolean alt = (Math.random() < 0.5);

        try { 
            fruit[0] = fruitFactory(alt); 
        } catch(Exception e) { System.out.println(e); } 
    } 

    private static Fruit fruitFactory(boolean apple) {
        if (apple) {
            return new Apple();
        } else {
            return new Orange();
        }
    } 
}

除了 fruit[0] 是由 fruitFactory 方法赋值外,代码与您的相同。编译器无法判断布尔值 alt 是 true 还是 false.

What information are missing to the compiler to realise that that assignment is not possible?

如前所述 - 编译器无法判断赋值是否可行。

Is there any cases in which such code is correct so no ArrayStoreException is thrown?

是的,在上面的代码中有 50% 的情况。您要么必须验证分配的对象与数组相同,要么捕获异常。