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

时间:2014-02-11 19:59:21

标签: java arrays generics arraylist

请考虑以下代码:

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

所以问题是:

  1. 数组如何记住它的类型(我知道它叫做“具体化”)。这究竟是怎么发生的?

  2. 为什么只有阵列被赋予这种能力但不是ArrayList,尽管它使用了一个阵列。

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

    < / LI>

    感谢。

5 个答案:

答案 0 :(得分:17)

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

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

  3. 编译器只知道arr2AA[]。如果您有AA[],则编译器只能假定它可以存储AA。编译器不会检测到类型安全问题,因为它只会在AA中放置BB[],因为它只能看到AA[]引用。与泛型不同,Java数组是协变的,因为BB[]AA[],因为BBAA。但是,这引入了您刚刚演示的内容的可能性 - 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)

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

    AA[] arr2=arr;
    
  4. 让问题变得更容易一些。你有一个车辆类,一辆自行车和一个延伸车辆的汽车类。你不能拥有

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

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