数组如何在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
。
所以问题是:
-
数组如何记住它的类型(我知道它被称为“具体化”)。 怎么会发生这种情况?
-
并且为什么只有arrays被赋予这种能力而不是
ArrayList
尽管它在它的引擎盖下使用了一个arrays。 -
为什么在编译时不能检测到
arr2[0] = a
,即当我执行arr2[0] = a
,它可能会导致编译器错误,而不是在运行时检测到它。
谢谢。
-
与generics不同,数组的类型信息存储在运行时。 从一开始它就是Java的一部分。 在运行时,
AA[]
可以与BB[]
区分开,因为JVM知道它们的类型。 -
ArrayList
(以及Collections框架的其余部分)使用generics,它受类型擦除的影响。 在运行时,generics类型参数不可用,因此ArrayList
与ArrayList
无法区分; 它们都只是JVM的ArrayList
。 -
编译器只知道
arr2
是AA[]
。 如果你有AA[]
,编译器只能假设它可以存储AA
。 编译器不会检测到类型安全问题,因为它只会在AA[]
那里放置AA
,因为它只能看到AA[]
引用。 与generics不同,Java数组是协变的,因为BB[]
是AA[]
因为BB
是AA
。 但是这引入了你刚才演示的可能性 – 一个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
,你会得到一个编译错误。 那么你对数组示例的期望 – 编译时间检测出现的不可能的任务 – 实际上与列表一起使用! 但是,如果省略generics类型参数,您将获得一个原始类型列表,您可以在没有合理类型检查的情况下为其分配其他列表。 这被认为是早期Java的遗留物,应该避免。 如果您在下面添加了以下行,则问题的代码如下:
BB item = listBB.get(0);
你认为应该/会编译吗? 它应该/将运行(如果是,它应该是什么结果)?
1)的部分可能保证单独的问题。
- 简单来说:数组就是一个类。 即使
BB
延伸AA
BB[]
也不会延伸AA[]
。 - 在早期的仿制药之前。
ArrayList
拥有对象。 所以任何东西都可以保存在ArrayList
。 如果你有一个Object of Array,你可以玩同一个游戏。 现在使用generics,您可以指定具有特定类型的ArrayList
,如ArrayList
。 -
实际上问题出在第2行:
AA[] arr2=arr;
让我们更容易解决问题。 你有一个车辆类,一辆自行车和一个延伸车辆的汽车类。 你不能拥有
Bike b=new Bike() Car c=b;
arr2是一个数组,arr是一个数组,但类是不同的。 这有帮助吗?