Dealing with an ArrayStoreException
Object[] o = "a;b;c".split(";");
o[0] = 42;
throws
java.lang.ArrayStoreException: java.lang.Integer
while
String[] s = "a;b;c".split(";");
Object[] o = new Object[s.length];
for (int i = 0; i < s.length; i++) {
o[i] = s[i];
}
o[0] = 42;
doesn't.
Is there any other way to deal with that exception without creating a temporary String[]
array?
Solution 1:
In Java an array is also an object.
You can put an object of a subtype into a variable of a supertype. For example you can put a String
object into an Object
variable.
Unfortunately, the array definition in Java is somehow broken. String[]
is considered a subtype of Object[]
, but that is wrong! For a more detailed explanation read about "covariance and contravariance", but the essence it this: A type should be considered a subtype of another type only if the subtype fulfills all obligations of the supertype. That means, that if you get a subtype object instead of a supertype object, you should not expect behavior contradictory to supertype contract.
Problem is that String[]
only supports a part of Object[]
contract. For example you can read Object
values from Object[]
. And you can also read Object
values (which happen to be String
objects) from String[]
. So far so good. Problem is with the other part of contract. You can put any Object
into Object[]
. But you cannot put any Object
into String[]
. Therefore, String[]
should not be considered a subtype of Object[]
, but Java specification says it is. And thus we have consequences like this.
(Note that a similar situation appeared again with the generic classes, but this time it was solved correctly. List<String>
is not a subtype of List<Object>
; and if you want to have a common supertype for these, you need List<?>
, which is read-only. This is how it should be also with arrays; but it's not. And because of the backwards compatibility, it is too late to change it.)
In your first example the String.split
function creates a String[]
object. You can put it into a Object[]
variable, but the object remains String[]
. This is why it rejects an Integer
value. You have to create a new Objects[]
array, and copy the values. You could use the System.arraycopy
function to copy the data, but you cannot avoid creating the new array.
Solution 2:
No, there is no way to avoid copying the array that split
returns.
The array that split
returns is actually a String[]
, and Java allows you to assign that to a variable of type Object[]
. It still is really a String[]
however, so when you try to store something else than a String
in it, you'll get an ArrayStoreException
.
For background information see 4.10.3. Subtyping among Array Types in the Java Language Specification.
Solution 3:
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