3

有人可以解释一下数组在 Java 中是如何工作的吗?

我对以下代码感到惊讶:

        Object test = new Object[2][2];
        Object test2 = new Object[] {
                new Object[2],new Object[2]
        };
        Object test3 = new Object[2][];
        ((Object[])test3)[0] = new Object[2];
        ((Object[])test3)[1] = new Object[2];
        System.out.println(test instanceof Object[]);
        System.out.println(test instanceof Object[][]);
        System.out.println(test2 instanceof Object[]);
        System.out.println(test2 instanceof Object[][]);
        System.out.println(test3 instanceof Object[]);
        System.out.println(test3 instanceof Object[][]);

只有 test2 不是 Object[][] 的实例

运行时有什么区别?

编辑:我看到了一些答案。Jon Skeet,请注意我可以:

Object[] test4 = (Object [])test;
test4[0] = "blaaa";
test4[1] = "toto";
System.out.println(test4);

test instanceof Object[] 返回 true,并且在演员表运行时不会引发异常。根据 Sierra & Bates 的 SCJP 书,测试 IS-A Object[][] 但也测试 Object[]

但是当试图用“test4[0] = “blaaa”;”重新分配一个新值时,我得到一个异常:线程“main”中的异常 java.lang.ArrayStoreException: java.lang.String at Main.main(Main.爪哇:24)

所以在运行时,test 和 test2 似乎都是 IS-A Object[],并且都包含对象数组,但其中只有一个 IS-A Object[][]

4

6 回答 6

12

Test2 仅被声明为对象数组。它包含的对象也恰好是数组,但这没有被声明。这就是区别。

于 2011-06-16T21:09:35.440 回答
8

test2指两个元素的数组。它的类型只是Object[]- 所以这些元素可以引用任何对象。特别是,你可以写:

// Valid
Object[] foo = (Object[]) test2;
foo[0] = "hello";

而这不适用于test

// Invalid - test isn't just an Object[], it's an Object[][]
Object[] foo = (Object[]) test;
test[0] = "hello";

因为test引用的数组的元素类型是Object[]而不是Object。数组“知道”每个元素应该为 null 或对 an 的引用Object[],因此 VM 将阻止它存储字符串。

您可以转换为 a 的方式与将 a 转换为testanObject[]的方式相同- 这称为数组协方差,在我看来,允许它是一个错误。正如我们所见,VM 必须在执行时检查存储。String[]Object[]

于 2011-06-16T21:10:55.393 回答
2

我没有找到我的问题的任何完整答案,我会这样做......

读完 SCJP 的书后,我就清楚了很多。它只是在泛型章节而不是数组一章中处理。(泛型与数组) Jon Skeet 的答案很好,但对我来说似乎不完整。

所以你必须了解泛型和数组的区别。

泛型只是一种“编译安全”。运行时没有检查。这意味着,通过以下技巧,您可以将 String 对象插入 Set

public static void main(String [] args) {
    Set<Integer> set = new HashSet<Integer>();
    set.add(1);
    set.add(2);
    addString(set,"test");
    for ( Object o : (Set)set ) {
        System.out.println(o);
    }
    for ( Object o : set ) {
        System.out.println(o);
    }
}

public static void addString(Set set,String s) {
    set.add(s);
}

输出是:

1
2
test
1
2
ClassCastException

http://www.ideone.com/nOSQz

请注意,代码编译(带有警告)并运行得非常好,直到您将该集合与Set<Integer>引用一起使用,因为有一个隐式转换试图将 String 转换为 Integer ...

这样做的原因有很多(书中并未全部公开),例如逆向兼容性,以及需要能够调用遗留代码(非泛型),同时提供非泛型集合。

如果你不调用任何遗留代码而只调用泛型代码,你不应该有任何这样的问题,你的集合只包含你想要的,因为编译器处理了它,并且不需要在运行时进行检查......

由于泛型在运行时没有检查并试图防止在集合中插入错误的项目,因此禁止将 a 强制转换List<Dog>为 aList<Animal>或使用参数调用 a method(List<Animal>) List<Dog>,因为这意味着您可以在 a 中插入 Cat List<Dog>by操纵List<Animal>...


数组的工作方式不同。

像 Jon Skeet sais 一样,数组具有类型协方差。

所以我们可以将 Dog[] 转换为 Animal[],因为 Dog 是一种动物。

与泛型一样,Java 想尽量避免将 Cats 插入 Dog 数组。但是由于协方差,它不能在编译时完成。像 Jon Skeet 一样,我同意这种协方差可能不是一个好主意,但它是 Java 的遗留问题......因此,与泛型相反,数组确实具有运行时检查以防止将猫插入到狗数组中。

在运行时,JVM 知道应该在我的数组中插入什么,而使用泛型则不知道。


所以,回到我最初的问题

Object test = new Object[2][2];
Object[] test2 = (Object [])test;
test2[0] = "blaaa";
test2[1] = "toto";
System.out.println(test2);

test(二维数组)可以转换为 test2(一维数组),但在幕后它仍然是一个二维数组,最后是一个一维数组 A1,它期望被其他包含对象的一维数组 A2 填充。

这就是为什么在 A1(最后是 test2)中我尝试插入一个字符串时引发 ArrayStoreException 的原因,该字符串最终是一个对象但不是对象 []


总结:

由于使用可以转换为 1D 数组的 1D 和 2D 数组,可能会有些混乱,但对于这段代码来说,它是完全相同的:

数组:

Dog[] dogs = new Dog[1];
dogs[0] = new Dog();
Animal[] animals = (Animal [])dogs;
animals[1] = new Cat();

这在第 4 行的运行时失败。而且您无法将猫插入到狗数组中。

如果我们对泛型做同样的事情

Set<Dog> dogs = new HashSet<Dog>();
dogs.add( new Dog() );
Set<Animal> animals = (Set<Animal>) dogs;
animals.add( new Cat() );

由于第 3 行,这无法编译。但是通过使用遗留的泛型代码,您可以将猫插入狗集。

于 2011-11-16T10:47:05.500 回答
1

test2 引用的对象是 Object[]。

Instanceof 正在测试 test2 引用的对象的类型,而不是数组内容的类型。

数组在运行时的内容是 Object[]s,它可以适合 Object[],因为 Object[]s 是 Objects。

于 2011-06-16T21:12:32.613 回答
0

test2是您可以放入任何东西的东西,Object因为它是通过new Object[]. 您只能将Object[]s 放入testandtest3因为它们是通过更严格的构造函数创建的 : new Object[][]

于 2011-06-16T21:10:24.067 回答
0

您已定义test2

Object test2 = new Object[];     // This is a plain array of Objects.
于 2011-06-16T21:18:37.417 回答