Sebastien Lorber
Sebastien Lorber

Reputation: 92210

How are java arrays really working

Can someone explain me how arrays really work in Java.

I was surprised by the following code:

        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[][]);

only test2 is not an instance of Object[][]

What is the distinction at runtime?

Edit: i see some answers. Jon Skeet, please notice that i can do:

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

test instanceof Object[] returns true, and no exception is raised at runtime on the cast. According to the SCJP book of Sierra & Bates, test IS-A Object[][] but also a Object[]

But when trying to reassigning a new value with "test4[0] = "blaaa";", i get an exception: Exception in thread "main" java.lang.ArrayStoreException: java.lang.String at Main.main(Main.java:24)

So it seems at runtime, both test and test2 IS-A Object[], and both contains object arrays, but only one of them IS-A Object[][]

Upvotes: 3

Views: 428

Answers (6)

Sebastien Lorber
Sebastien Lorber

Reputation: 92210

I don't find any complete answer to my question to i'll do it...

After finishing the reading of SCJP book, it's a lot clearer to me. It was just treated on the generics chapter rather than arrays one. (Generics vs Arrays) Jon Skeet answer is good but seems incomplete to me.

So you have to understand the differences with generics and arrays.

Generics are just a "compilation security". There is no check at runtime. This means, by the following trick, you CAN insert String objets into a 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);
}

The output is:

1
2
test
1
2
ClassCastException

http://www.ideone.com/nOSQz

Note that the code compile (with a warning) and runs perfectly fine, until you use that set with a Set<Integer> reference because there is an implicit cast that tries to cast String to Integer...

This has been done this way because of many reasons (not all exposed on the book) like retrocompatibility, and the need to be able to call legacy code (non generic), while providing non generic collections.

If you don't call any legacy code but only generics code, you should not have any problem like that, your collection contains only what you want since the compiler handled it, and there is no need to do checks at runtime...

Since generics do not have checks at runtime and try to prevent inserting the wrong item in a collection, it is forbidden to cast a List<Dog> to a List<Animal>, or to call a method(List<Animal>) with a List<Dog> param, because this would mean you could insert a Cat in a List<Dog> by manipulating a List<Animal>...


Arrays do not work the same.

An array have, like Jon Skeet sais, type covariance.

So we can convert a Dog[] to an Animal[] since Dog is an animal.

Like for generics, Java want to try to avoid inserting Cats to a Dog array. But because of covariance, it can't be done at compile time. Like Jon Skeet, i agree this covariance is perhaps not a good idea, but it's java legacy... So, in opposition to generics, arrays do have runtime check to prevent inserting cats to a dog array.

At runtime, the JVM knows what should be inserted in my arrays, while with generics it doesn't.


So, back to my initial question

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

test (2D array) can be casted to test2 (1D array), but behind the scene it is still a 2D array, which is finally, a 1D array A1 that expects to be filled with other 1D arrays A2's which contains Objects.

That's why an ArrayStoreException is raised when in A1 (finally test2) i try to insert a String, which is finally an Object but not an Object[]


To conclude:

There may be a little confusion because of using 1D and 2D arrays that can be casted to 1D arrays, but it would be exactly the same for this code:

Arrays:

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

This fails at runtime on the 4th line. And there is no way you can insert a cat into a dog array.

And if we do the same with generics

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

This doesn't compile because of the 3rd line. But by using legacy generics code you CAN insert a cat to a dogs set.

Upvotes: 2

Jon Skeet
Jon Skeet

Reputation: 1503944

test2 refers to an array of two elements. Its type is just Object[] - so those elements can refer to any objects. In particular, you could write:

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

whereas that wouldn't work for test:

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

because the element type of the array that test refers to is Object[] rather than Object. The array "knows" that each element should be null or a reference to an Object[], so the VM will prevent it from storing a string.

You can convert test to an Object[] in the same way that you can convert a String[] to an Object[] - that's called array covariance, and it was a mistake to allow it in my opinion. The VM has to check stores at execution time, as we've seen.

Upvotes: 8

fastcodejava
fastcodejava

Reputation: 41127

You have defined test2 as

Object test2 = new Object[];     // This is a plain array of Objects.

Upvotes: 0

Andy Thomas
Andy Thomas

Reputation: 86509

The object referenced by test2 is an Object[].

Instanceof is testing the type of the object referenced by test2, not the type of the array's contents.

The array's contents at runtime are Object[]s, which can fit into the Object[] because Object[]s are Objects.

Upvotes: 1

Mike Samuel
Mike Samuel

Reputation: 120586

test2 is something that you can put any Object into because it was created via new Object[]. You can only put Object[]s into test and test3 because they were created via a constructor that is stricter : new Object[][].

Upvotes: 0

Jean-Bernard Pellerin
Jean-Bernard Pellerin

Reputation: 12680

Test2 is only declared to be an array of objects. The objects it contains happen to be arrays as well, but this is not declared. That is the distinction.

Upvotes: 12

Related Questions