Как на самом деле работают Java-массивы - PullRequest
3 голосов
/ 17 июня 2011

Может кто-нибудь объяснить мне, как массивы действительно работают в 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 [] []

Какая разница во время выполнения?

Редактировать: я вижу некоторые ответы. Джон Скит, пожалуйста, обратите внимание, что я могу сделать:

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

test instanceof Object [] возвращает true, и исключение не вызывается во время выполнения приведения. Согласно книге SCJP Sierra & Bates, тестируйте объект IS-A [] [], но также и объект []

Но при попытке переназначения нового значения с помощью "test4 [0] =" blaaa ";", я получаю исключение: Исключение в потоке "main" java.lang.ArrayStoreException: java.lang.String на Main.main (Main.java:24)

Похоже, что во время выполнения, test и test2 IS-A Object [], и оба содержат объектные массивы, но только один из них IS-A Object [] []

Ответы [ 6 ]

12 голосов
/ 17 июня 2011

Test2 объявляется только как массив объектов. Объекты, которые он содержит, также являются массивами, но это не объявлено. Это и есть различие.

8 голосов
/ 17 июня 2011

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 или ссылку на Object[], поэтому виртуальная машина не позволит ему сохранить строку.

Вы можете преобразовать test в Object[] точно так же, как вы можете преобразовать String[] в Object[] - это называется ковариация массива , и было допущено, что это по моему. Как мы видели, виртуальная машина должна проверять хранилища во время выполнения.

2 голосов
/ 16 ноября 2011

Я не нахожу полного ответа на мой вопрос, потому что я сделаю это ...

После прочтения книги SCJP мне стало намного понятнее.Это было рассмотрено только в главе об обобщениях, а не в массивах.(Generics vs Arrays) Ответ Джона Скита хорош, но мне кажется неполным.

Так что вы должны понимать разницу с универсальными и массивами.

Дженерики - это просто "безопасность компиляции".Там нет проверки во время выполнения.Это означает, что с помощью следующего трюка вы МОЖЕТЕ вставить строковые объекты в набор

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 ...

Это было сделаноЭто объясняется многими причинами (не все раскрыты в книге), такими как ретро-совместимость и необходимость иметь возможность вызывать устаревший код (не универсальный), в то же время предоставляя не универсальные коллекции.

Если вы не вызываете какие-либоУнаследованный код, но только обобщенный код, у вас не должно быть таких проблем, ваша коллекция содержит только то, что вы хотите, так как компилятор ее обработал, и нет необходимости делать проверки во время выполнения ...

Поскольку обобщенные элементыне проверять во время выполнения и пытаться предотвратить вставку неправильного элемента в коллекцию, запрещено приводить List<Dog> к List<Animal> или вызывать method(List<Animal>) с параметром List<Dog>, bпотому что это может означать, что вы можете вставить Cat в List<Dog>, манипулируя List<Animal> ...


Массивы не работают одинаково.

Массив имеет, например,Jon Skeet sais, введите covariance.

Таким образом, мы можем преобразовать Dog [] в Animal [], так как Dog - животное.

Как и в случае с дженериками, Java хочет попытаться избежать вставки Catsк массиву собак.Но из-за ковариации это не может быть сделано во время компиляции.Как и Джон Скит, я согласен, что эта ковариация, возможно, не очень хорошая идея, но это наследие Java ... Так что, в отличие от генериков, массивы действительно имеют проверку во время выполнения, чтобы предотвратить вставку кошек в массив собак.

ВВо время выполнения JVM знает, что должно быть вставлено в мои массивы, в то время как с генериками это не так.


Итак, вернемся к моему первоначальному вопросу

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

test (2D array) можно преобразовать в test2 (массив 1D), но за сценой это все еще двумерный массив, который, наконец, является одномерным массивом A1, который, как ожидается, будет заполнен другими одномерными массивами A2, которые содержат объекты.

Вот почему возникает ArrayStoreException, когда в A1 (наконец, test2) я пытаюсь вставить строку, которая, наконец, является объектом, но не объектом []


В заключение:

Тамможет быть небольшая путаница из-за использования 1D и 2D-массивов, которые могут быть преобразованы в 1D-массивы, но это будет точно так же для этого кода:

Массивы:

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-й строки.Но с помощью устаревшего универсального кода вы МОЖЕТЕ вставить кошку в набор собак.

1 голос
/ 17 июня 2011

Объект, на который ссылается test2, является объектом [].

Instanceof проверяет тип объекта, на который ссылается test2, а не тип содержимого массива.

Содержимое массива во время выполнения - это Object [], которые могут вписываться в Object [], потому что Object [] - это Object.

0 голосов
/ 17 июня 2011

Вы определили test2 как

Object test2 = new Object[];     // This is a plain array of Objects.
0 голосов
/ 17 июня 2011

test2 - это то, что вы можете поместить в Object, потому что оно было создано с помощью new Object[]. Вы можете поместить Object[] s в test и test3 только потому, что они были созданы с помощью более строгого конструктора: new Object[][].

...