数组如何在Java中“记住”它们的类型?

2022-09-02 03:48:29

请考虑以下代码:

class AA { }

class BB extends AA { }

public class Testing {

    public static void main(String[] args) {
        BB[] arr = new BB[10];
        AA[] arr2 = arr;

        BB b = new BB();
        AA a = new AA();
        arr2[0] = a; // ArrayStoreException at runtime
        arr2[1] = b;

        List<BB> listBB = new ArrayList<>();
        List listAA = listBB;
        listAA.add("hello world.txt");

    }
}

在上面的例子中,当我尝试时,我得到了.这意味着数组记住它必须接受的类型。但是不记得他们。它只是编译并运行良好。当我检索对象时将抛出。ArrayStoreExceptionarr2[0] = aListClassCastExceptionBB

所以问题是:

  1. 数组如何记住其类型(我知道它被称为“重新化”)。这究竟是如何发生的?

  2. 以及为什么只有数组被赋予这种能力,尽管它在其引擎盖下使用数组,却没有。ArrayList

  3. 为什么在编译时无法检测到,即当我这样做时,它可能会导致编译器错误,而不是在运行时检测到它。ArrayStoreExceptionarr2[0] = a

谢谢。


答案 1
  1. 与泛型不同,数组的类型信息存储在运行时。从一开始,这一直是Java的一部分。在运行时,可以将 a 与 区分,因为 JVM 知道它们的类型。AA[]BB[]

  2. An(以及集合框架的其余部分)使用泛型,泛型需要进行类型擦除。在运行时,泛型类型参数不可用,因此 an 与 ;它们都只是JVM的s。ArrayListArrayList<BB>ArrayList<AA>ArrayList

  3. 编译器只知道 这是一个 .如果你有 一个 ,编译器只能假设它可以存储一个 .编译器不会检测到类型安全问题,因为您将 a 放在真正的 a 中,因为它只能看到引用。与泛型不同,Java 数组是协变的,因为 a 是一个,因为 a 是一个 .但是这引入了您刚刚演示的可能性 - an ,因为 所引用的对象实际上是 a ,它不会将 a 作为元素处理。arr2AA[]AA[]AAAABB[]AA[]BB[]AA[]BBAAArrayStoreExceptionarr2BB[]AA


答案 2

1. 每次将值存储到数组中时,编译器都会插入一个检查项。然后在运行时,它验证值的类型是否等于数组的运行时类型。

2. 引入了泛型。泛型是不变的,可以在编译时进行验证。(在运行时,泛型类型将被擦除)。

3. 以下是失败案例的一个例子(来自维基百科):

// a is a single-element array of String
String[] a = new String[1];

// b is an array of Object
Object[] b = a;

// Assign an Integer to b. This would be possible if b really were
// an array of Object, but since it really is an array of String,
// we will get a java.lang.ArrayStoreException.
b[0] = 1;

编译器无法检测到第三条语句将导致 .关于第三个语句,编译器看到我们正在向 Object[] 数组添加一个 Integer。这是完全合法的。ArrayStoreException

背景/推理(来自维基百科))

Java和C#的早期版本不包括泛型(又名参数化多态性)。在这样的设置中,使数组不变会排除有用的多态程序。例如,考虑编写一个函数来随机排列数组,或者编写一个函数,使用元素上的 Object.equals 方法测试两个数组的相等性。该实现不依赖于数组中存储的元素的确切类型,因此应该可以编写一个适用于所有类型的数组的函数。易于实现类型的功能

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

但是,如果数组类型被视为不变的,则只能在类型完全相同的 Object[] 的数组上调用这些函数。例如,不能对字符串数组进行随机排序。

因此,Java 和 C# 都以协变方式处理数组类型。例如,在 C# 中,string[] 是 object[] 的子类型,在 Java 中 String[] 是 Object[] 的子类型。