请考虑以下代码:
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");
}
}
在上面的例子中,当我尝试ArrayStoreException
时,我得到arr2[0] = a
。这意味着数组会记住它必须接受的类型。但是List
不记得它们。它只是编译并运行良好。当我检索对象ClassCastException
时,将抛出BB
。
所以问题是:
数组如何记住它的类型(我知道它叫做“具体化”)。这究竟是怎么发生的?
为什么只有阵列被赋予这种能力但不是ArrayList
,尽管它使用了一个阵列。
为什么在编译时不能检测到ArrayStoreException
,即当我执行arr2[0] = a
时,它可能会导致编译器错误,而不是在运行时检测到它。
感谢。
答案 0 :(得分:17)
与泛型不同,数组的类型信息存储在运行时。从一开始它就是Java的一部分。在运行时,可以将AA[]
与BB[]
区分开来,因为JVM知道它们的类型。
ArrayList
(以及Collections框架的其余部分)使用泛型,它受类型擦除的影响。在运行时,泛型类型参数不可用,因此ArrayList<BB>
与ArrayList<AA>
无法区分;它们只是JVM的ArrayList
。
编译器只知道arr2
是AA[]
。如果您有AA[]
,则编译器只能假定它可以存储AA
。编译器不会检测到类型安全问题,因为它只会在AA
中放置BB[]
,因为它只能看到AA[]
引用。与泛型不同,Java数组是协变的,因为BB[]
是AA[]
,因为BB
是AA
。但是,这引入了您刚刚演示的内容的可能性 - ArrayStoreException
,因为arr2
引用的对象实际上是BB[]
,它不会处理AA
元件。
答案 1 :(得分:8)
<强> 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;
编译器无法检测到第三个语句将导致ArrayStoreException
。关于第三个语句,编译器看到我们正在向Object []数组添加一个Integer。这是完全合法的。
背景/推理(来自wikipedia)
早期版本的Java和C#不包含泛型(a.k.a.参数多态)。在这样的设置中,使数组不变可以排除有用的多态程序。 例如,考虑编写一个函数来混淆数组,或者使用元素上的Object.equals方法测试两个数组是否相等的函数。实现不依赖于存储在数组中的元素的确切类型,因此应该可以编写适用于所有类型数组的单个函数。很容易实现类型
的功能
boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);
但是,如果将数组类型视为不变量,则只能在完全类型为Object []的数组上调用这些函数。例如,人们无法改变一系列字符串。
因此,Java和C#都会协同处理数组类型。例如,在C#中,string []是object []的子类型,而在Java String []中是Object []
的子类型
答案 2 :(得分:2)
Arrays明确记住它们的类型,你甚至可以在运行时获取它(array.getClass()。getComponentType())。
当存储到数组时,VM将检查要存储的元素是否与数组的组件类型兼容。如果不是,则会得到ArrayStoreException。
集合(作为ArrayList)在内部将其支持数组声明为Object [],因此它们可以存储任何内容,甚至是不允许按通用定义存储的类型。
答案 3 :(得分:1)
3)无论你是AA a = new AA();
还是AA a = new BB();
,编译器都不记得你以后分配给a
的内容,只是它声明的类型是AA
。但是,在后一种情况下,您实际上可以将a
的值分配给BB[]
的元素,因此arr2[0] = a;
不应该为您提供运行时异常。因此,编译器无法提前告知。 (此外,您可以尝试讨厌的事情来在运行时在各行之间更改a
的值...)
2)如果你使用List<AA> listAA = listBB;
,你会遇到编译错误。那么你对数组示例的期望 - 编译时间检测一个不可能的任务 - 实际上与列表一起工作!但是,如果省略泛型类型参数,您将获得一个原始类型列表,您可以在没有合理类型检查的情况下为其分配其他列表。这被认为是早期Java的遗留物,应该避免。如果您在下面添加了以下行,则问题代码为:
BB item = listBB.get(0);
你认为应该/会编译吗?它应该/将运行(如果是,它应该是什么结果)?
1)的 部分可能需要另外提出一个问题。
答案 4 :(得分:1)
BB[]
延伸AA[]
,BB
也不会延伸AA
。 ArrayList
持有物件。所以任何东西都可以保存在ArrayList
中。如果你有一个Object
的数组,你可以玩同一个游戏。现在使用泛型,您可以指定ArrayList
,其具有特定类型,例如ArrayList<String>
。实际上问题出在第2行:
AA[] arr2=arr;
让问题变得更容易一些。你有一个车辆类,一辆自行车和一个延伸车辆的汽车类。你不能拥有
Bike b=new Bike()
Car c=b;
arr2是一个数组,arr是一个数组,但是类是不同的。这有帮助吗?