Object[] o = "a;b;c".split(";");
o[0] = 42;
引发
java.lang.ArrayStoreException: java.lang.Integer
,而
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;
不
有没有其他方法可以在不创建临时String[]
数组的情况下处理该异常?
答案 0 :(得分:69)
在Java中,数组也是对象。
您可以将子类型的对象放入超类型的变量中。例如,您可以将String
对象放入Object
变量。
不幸的是,Java中的数组定义在某种程度上被打破了。 String[]
被视为Object[]
的子类型,但错误!有关“协方差和逆变”的更详细解释,但其实质是:只有当子类型满足超类型的所有义务时,才应将类型视为另一种类型的子类型。这意味着,如果你得到一个子类型对象而不是超类型对象,你不应该期望与超类型契约相矛盾的行为。
问题是String[]
仅支持Object[]
合同的部分。例如,您可以从Object
读取 Object[]
值。您还可以从Object
读取 String
值(恰好是String[]
个对象)。到现在为止还挺好。问题在于合同的其他部分。您可以将任何 Object
放入Object[]
。但您无法将任何 Object
放入String[]
。因此,String[]
不应被视为Object[]
的子类型,但Java规范表明它是。{1}}。因此我们有这样的后果。
(请注意,类似的情况再次出现在泛型类中,但这一次是正确解析。List<String>
不是 {{em>的子类型1}};如果你想为这些提供一个共同的超类型,你需要List<Object>
,它是只读的。这就是它应该如何与数组;但它不是。而且由于向后兼容,改变它为时已晚。)
在第一个示例中,List<?>
函数创建了一个String.split
对象。您可以将其放入String[]
变量,但对象仍为Object[]
。这就是它拒绝String[]
值的原因。您必须创建一个新的Integer
数组,然后复制这些值。您可以使用Objects[]
函数复制数据,但无法避免创建新数组。
答案 1 :(得分:9)
不,没有办法避免复制split
返回的数组。
split
返回的数组实际上是String[]
,Java允许您将其分配给Object[]
类型的变量。但它仍然是String[]
,所以当您尝试在其中存储除String
之外的其他内容时,您将获得ArrayStoreException
。
有关背景信息,请参阅Java语言规范中的4.10.3. Subtyping among Array Types。
答案 2 :(得分:1)
这是许多月前Java开发人员讨价还价的结果。尽管看起来有些奇怪,但是此功能对于许多方法都很重要,例如Arrays.sort
(也恰好在Collections.sort
中被调用)。基本上,如果不将X [](其中X是Object的某个子类)视为子类型,则将Object []作为参数的任何方法都将按预期停止执行。例如,在某些情况下,可能需要对数组进行重新整理,使其成为只读,但是问题变成了“何时?”。
一方面,将已作为参数传递给方法的数组设为只读可能会阻碍编码器进行原位修改的能力。另一方面,在将数组作为参数传递时设置异常将仍然允许编码器进行非法修改,例如在调用方传递Integer数组时存储String。
但是说“例如Integer []不是Object []的子类型”的结果是一种危机,其中必须为Object []和Integer []创建单独的方法。通过扩展这种逻辑,我们可以进一步说必须为String [],Comparable []等创建一个单独的方法。每种数组类型都需要一个单独的方法,即使这些方法完全相同。
这正是我们具有多态性的情况。
不幸的是,这里允许多态确实允许尝试将值非法存储在数组中,如果发生这种情况,则会抛出ArrayStoreException
。但是,这是一个很小的代价,并且与ArrayIndexOutOfBoundsException
一样可以避免。
ArrayStoreException
在大多数情况下可以通过两种方式(尽管您无法控制其他人的行为)轻松避免。
1)
不要在不知道其 actual 组件类型的情况下尝试将对象存储在数组中。当您正在使用的数组已传递到方法中时,您不一定知道它的来源,因此除非组件类型的类是final(即没有子类),否则您不能假设它是安全的。>
如果数组是从方法返回的,如上面的问题,请了解该方法。实际类型是否可能是返回类型的子类?如果是这样,您必须考虑到这一点。
2)
首次初始化在本地使用的数组时,请使用格式X[] blah = new X[...];
或X[] blah = {...};
或(从Java 10开始){ {1}}。然后,任何在该数组中存储非X值的尝试都将导致编译器错误。您不应该说的是var blah = new X[...];
,其中X是Y的子类。
如果您有一个数组,例如上面的问题,要在其中存储类型错误的组件,那么就像其他人建议的那样,您必须创建一个正确类型的新数组并将信息复制到...
Y[] blah = new X[...];
或者您必须以某种方式将要存储的组件转换为正确的类型。
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;
请注意42!=“ 42”,因此在决定采用哪条路径时,应考虑如何影响其余代码。
我只想结束关于泛型的注释(如先前的回答所述)。实际上,泛型同样能够使毫无戒心的编码器感到惊讶。考虑以下代码段(从here修改)。
s[0] = String.valueOf(42);
仿制药,女士们,先生们。 :p
答案 3 :(得分:0)
当然还有其他选项,比如你实现自己的split方法,它直接返回一个Object数组。我不确定临时String数组究竟是什么困扰你?
顺便说一句,您可以使用System.arrayCopy通过几行来缩短代码,而不是实现自己的循环来复制数组元素:System.arrayCopy(s, 0, o, 0, s.length);