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

请考虑以下代码:

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 listBB = new ArrayList(); List listAA = listBB; listAA.add("hello world.txt"); } } 

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

所以问题是:

  1. 数组如何记住它的类型(我知道它被称为“具体化”)。 怎么会发生这种情况?

  2. 并且为什么只有arrays被赋予这种能力而不是ArrayList尽管它在它的引擎盖下使用了一个arrays。

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

谢谢。

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

  2. ArrayList (以及Collections框架的其余部分)使用generics,它受类型擦除的影响。 在运行时,generics类型参数不可用,因此ArrayListArrayList无法区分; 它们都只是JVM的ArrayList

  3. 编译器只知道arr2AA[] 。 如果你有AA[] ,编译器只能假设它可以存储AA 。 编译器不会检测到类型安全问题,因为它只会在AA[]那里放置AA ,因为它只能看到AA[]引用。 与generics不同,Java数组是协变的,因为BB[]AA[]因为BBAA 。 但是这引入了你刚才演示的可能性 – 一个ArrayStoreException ,因为arr2引用的对象实际上是一个BB[] ,它不会将AA作为一个元素来处理。

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

2.介绍了仿制药。 generics是不变的,可以在编译时进行validation。 (在运行时,擦除generics类型)。

这是一个失败案例的例子(来自维基百科):

 // 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; 

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

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

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

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

但是,如果将数组类型视为不变量,则只能在完全类型为Object []的数组上调用这些函数。 例如,人们无法改变一系列字符串。

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

数组明确记住它们的类型,你甚至可以在运行时获取它(array.getClass()。getComponentType())。

存储到数组时,VM将检查要存储的元素是否与数组的组件类型兼容。 如果不是,则会得到ArrayStoreException。

集合(作为ArrayList)在内部将其支持数组声明为Object [],因此它们可以存储任何内容,甚至是它们的通用定义不允许存储的类型。

3)你是否做AA a = new AA();AA a = new BB(); ,编译器以后不记得你分配给a ,只是它声明的类型是AA 。 但是,在后一种情况下,您实际上可以将a的值赋给BB[]的元素,因此arr2[0] = a; 不应该给你一个运行时exception。 因此,编译器无法提前告知。 (此外,你可以尝试讨厌的东西来改变各个行之间的运行时的值……)

2)您是否使用过List listAA = listBB; ,你会得到一个编译错误。 那么你对数组示例的期望 – 编译时间检测出现的不可能的任务 – 实际上与列表一起使用! 但是,如果省略generics类型参数,您将获得一个原始类型列表,您可以在没有合理类型检查的情况下为其分配其他列表。 这被认为是早期Java的遗留物,应该避免。 如果您在下面添加了以下行,则问题的代码如下:

 BB item = listBB.get(0); 

你认为应该/会编译吗? 它应该/将运行(如果是,它应该是什么结果)?

1)的部分可能保证单独的问题。

  1. 简单来说:数组就是一个类。 即使BB延伸AA BB[]也不会延伸AA[]
  2. 在早期的仿制药之前。 ArrayList拥有对象。 所以任何东西都可以保存在ArrayList 。 如果你有一个Object of Array,你可以玩同一个游戏。 现在使用generics,您可以指定具有特定类型的ArrayList ,如ArrayList
  3. 实际上问题出在第2行:

     AA[] arr2=arr; 

让我们更容易解决问题。 你有一个车辆类,一辆自行车和一个延伸车辆的汽车类。 你不能拥有

 Bike b=new Bike() Car c=b; 

arr2是一个数组,arr是一个数组,但类是不同的。 这有帮助吗?