This is a result of something of a bargain on the part of Java developers many moons ago. While it may seem odd, this functionality is important for many methods, such as Arrays.sort
(which also happens to be invoked in Collections.sort
). Basically, any method that takes an Object[] as a parameter would cease to perform as intended if X[] (where X is some subclass of Object) were not considered a subtype. It is possible that arrays could have been reworked such that under certain circumstances they were read-only, for example, but then the question becomes "when?".
On the one hand, making arrays that have been passed into a method as arguments read-only could hinder the ability of the coder to make in situ modifications. On the other hand, making an exception for when an array is passed as an argument would still allow the coder to make illegal modifications, such as storing a String when an Integer array is what was passed by the caller.
But the result of saying "Integer[] (for example) is not a subtype of Object[]" is a crisis wherein one must create a separate method for Object[] and Integer[]. By extension of such logic we can further say a separate method must be created for String[], Comparable[], etc. Every type of array would require a separate method, even if those methods were otherwise exactly the same.
This is exactly the kind of situation for which we have polymorphism.
Allowing for polymorphism here though does, unfortunately, allow for attempt to illegally store a value in an array, and an ArrayStoreException
is thrown if such an instance occurs. However, this is a small price to pay, and no less avoidable than an ArrayIndexOutOfBoundsException
.
ArrayStoreException
can be easily prevented in most cases in two ways(though you can't control what others do).
1)
Don't try and store objects in an array without knowing it's actual component type. When the array you are working with has been passed into method, you don't necessarily know where it's coming from, so you can't assume it is safe unless the class of the component type is final(i.e. no subclasses).
If the array is returned from method, like in question above, get to know the method. Is it possible that the actual type is subclass of return type? If so you must take this into account.
2)
When you first initialize an array that are working with locally, use the form X[] blah = new X[...];
or X[] blah = {...};
or (as of Java 10) var blah = new X[...];
. Then, any attempt to store a non-X value in this array will result in a compiler error. What you should not say is Y[] blah = new X[...];
, where X is a subclass of Y.
If you have an array, like in question above, where want to store components that are the wrong type, then like others have suggest, you must either create a new array of the proper type and copy the information in...
Object[] o = Arrays.copyOf(s, s.length, Object[].class); //someone demonstrate System.arrayCopy. I figure I show another way to skin cat. :p
o[0] = 42;
or you must in some way convert the components you want to store into the proper type.
s[0] = String.valueOf(42);
Note that 42 != "42" so in making a decision which path to take, should consider how will affect the rest of your code.
I'd just like to end on a note regarding generics (as addressed in an earlier answer). Generics are actually just as capable of surprising the unsuspecting coder. Consider the following code snippet, (modified from here).
import java.util.List;
import java.util.ArrayList;
public class UhOh {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
WildcardFixed.foo(list);
list.add(6);
System.out.println(list); // ¯\_(ツ)_/¯ oh well.
int i = list.get(0); //if we're going to discuss breaches of contract... :p
}
}
class WildcardFixed /*not anymore ;) */ {
static void foo(List<?> i) {
fooHelper(i);
}
private static <T> void fooHelper(List<T> l) {
l.add((T)Double.valueOf(2.5));
}
}
Generics, ladies and gentlemen. :p