At the end of Chapter 4, we mentioned that arrays have a place in the Java class hierarchy, but we didn't give you any details. Now that we've discussed the object-oriented aspects of Java, we can give you the whole story.
Array classes live in a parallel Java class hierarchy under the Object
class. If a class is a direct subclass of Object,
then an array class for that base type also exists as a direct subclass
of Object. Arrays of more derived classes are
subclasses of the corresponding array classes. For example, consider the
following class types:
class Animal { ... }
class Bird extends Animal { ... }
class Penguin extends Bird { ... } Figure 6.8 illustrates the class hierarchy for arrays
of these classes. Arrays of the same dimension are related to one another in the same manner
as their base type classes. In our example, Bird
is a subclass of Animal, which means that the
Bird[] type is a subtype of Animal[].
In the same way a Bird object can be used
in place of an Animal object, a Bird[]
array can be assigned to an Animal[] array:

Animal [][] animals; Bird [][] birds = new Bird [10][10]; birds[0][0] = new Bird(); // make animals and birds reference the same array object animals = birds; System.out.println( animals[0][0] ); // prints Bird
Because arrays are part of the class hierarchy, we can use instanceof
to check the type of an array:
if ( birds instanceof Animal[][] ) // yes
An array is a subtype of Object and can therefore
be assigned to Object type variables:
Object something; something = animals;
Since Java knows the actual type of all objects, you can also cast back if appropriate:
animals = (Animal [][])something;
Under unusual circumstances, Java may not be able to check the types
of objects you place into arrays at compile-time. In those cases, it's
possible to receive an ArrayStoreException if you
try to assign the wrong type of object to an array element. Consider
the following:
class Dog { ... }
class Poodle extends Dog { ... }
class Chihuahua extends Dog { ... }
Dog [] dogs;
Poodle [] poodles = new Poodle [10];
dogs = poodles;
dogs[3] = new Chihuahua(); // Run-time error, ArrayStoreException Both Poodle and Chihuahua are
subclasses of Dog, so an array of
Poodle objects can therefore be assigned to an
array of Dog objects, as I described
previously. The problem is that an object assignable to an
element of an array of type Dog[] may not be
assignable to an element of an array of type
Poodle. A Chihuahua object, for
instance, can be assigned to a Dog element because
it's a subtype of Dog, but not to a
Poodle element.[4]
[4] In some sense this could be considered a hole in the Java type system. It doesn't occur elsewhere in Java, only with arrays. This is because array objects exhibit covariance in overriding their assignment and extraction methods. Covariance allows array subclasses to override methods with arguments or return values that are subtypes of the overridden methods, where the methods would normally be overloaded or prohibited. This allows array subclasses to operate on their base types with type safety, but also means that subclasses have different capabilities than their parents, leading to the problem shown above.