53
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[]有没有其他方法可以在不创建临时数组的情况下处理该异常?

4

4 回答 4

91

在 Java 中,数组也是一个对象

您可以将子类型的对象放入超类型的变量中。例如,您可以将String对象放入Object变量中。

不幸的是,Java 中的数组定义不知何故被破坏了。String[]被认为是亚型的Object[],但那是错误的!有关“协变和逆变”的更详细解释,但其本质是:只有当子类型满足超类型的所有义务时,才应将类型视为另一种类型的子类型。这意味着,如果你得到一个子类型对象而不是超类型对象,你不应该期望与超类型契约相矛盾的行为。

问题是String[]只支持合同的一部分Object[]例如,您可以从中读取 ObjectObject[]。您还可以 . 到现在为止还挺好。问题在于合同的另一部分。您可以将任何放入. 但是您不能将任何内容放入. 因此,不应将其视为 的子类型,但 Java 规范说它是。因此我们有这样的后果。ObjectStringString[] ObjectObject[] ObjectString[]String[]Object[]

(请注意,类似的情况再次出现在泛型类中,但这次它被正确解决了它应该如何与数组一起使用;但事实并非如此。而且由于向后兼容,现在更改它为时已晚。)List<String>List<Object>List<?>

在您的第一个示例中,该String.split函数创建一个String[]对象。您可以将其放入Object[]变量中,但对象仍然是String[]. 这就是它拒绝一个Integer值的原因。您必须创建一个新Objects[]数组,并复制这些值。您可以使用该System.arraycopy函数复制数据,但不能避免创建新数组。

于 2012-09-11T12:56:51.013 回答
11

不,没有办法避免复制split返回的数组。

返回的数组split实际上是 a String[],Java 允许您将它分配给 type 的变量Object[]。然而,它仍然是真正的 a String[],因此当您尝试在其中存储除 a 之外的其他内容String时,您会得到一个ArrayStoreException.

背景信息见4.10.3。Java 语言规范中的数组类型之间的子类型化。

于 2012-09-11T12:44:01.670 回答
1

这是许多个月前 Java 开发人员讨价还价的结果。虽然看起来很奇怪,但此功能对于许多方法都很重要,例如Arrays.sort(也恰好在 中调用Collections.sort)。基本上,如果 X[](其中 X 是 Object 的某个子类)不被视为子类型,则任何将 Object[] 作为参数的方法都将停止按预期执行。例如,数组可能已被重新设计,以至于在某些情况下它们是只读的,但是问题变成了“何时?”。

一方面,将作为参数传递给方法的数组设为只读可能会阻碍编码人员进行原位修改的能力。另一方面,当数组作为参数传递时例外,仍然允许编码器进行非法修改,例如当调用者传递一个整数数组时存储一个字符串。

但是说“Integer[](例如)不是 Object[] 的子类型”的结果是一个危机,其中必须为 Object[] 和 Integer[] 创建一个单独的方法。通过扩展这种逻辑,我们可以进一步说必须为 String[]、Comparable[] 等创建一个单独的方法。每种类型的数组都需要一个单独的方法,即使这些方法在其他方面完全相同

这正是我们拥有多态性的那种情况。

不幸的是,在这里允许多态性确实允许尝试在数组中非法存储一个值,并且ArrayStoreException如果发生这样的实例,则会抛出 an 。然而,这是一个很小的代价,并且与ArrayIndexOutOfBoundsException.

ArrayStoreException在大多数情况下,可以通过两种方式轻松预防(尽管您无法控制其他人的行为)。

1)不要在不知道 实际组件类型
的情况下尝试将对象存储在数组中。当您正在使用的数组被传递到方法中时,您不一定知道它来自哪里,因此您不能假设它是安全的,除非组件类型的类是最终的(即没有子类)。

如果数组是从方法返回的,就像上面的问题一样,请了解该方法。实际类型是否可能是返回类型的子类?如果是这样,您必须考虑到这一点。

2)
当你第一次初始化一个在本地使用的数组时,使用形式X[] blah = new X[...];or X[] blah = {...};or (as of Java 10) var blah = new X[...];。然后,任何在此数组中存储非 X 值的尝试都将导致编译器错误。你不应该说的是Y[] blah = new X[...];,其中 X 是 Y 的子类。

如果您有一个数组,就像上面的问题一样,想要存储错误类型的组件,那么就像其他人建议的那样,您必须创建一个正确类型的新数组并将信息复制到...

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;

或者您必须以某种方式将要存储的组件转换为正确的类型。

s[0] = String.valueOf(42);

请注意, 42 != "42" 因此在决定采用哪条路径时,应考虑将如何影响其余代码。

我只想以关于泛型的说明结束(如之前的答案中所述)。泛型实际上同样能够让毫无戒心的编码人员感到惊讶。考虑以下代码片段(从此处修改)。

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));
    }
}

泛型,女士们,先生们。:p

于 2019-02-07T23:11:52.247 回答
0

当然还有其他选择,比如你实现自己的 split 方法,它直接返回一个 Object 数组。我不确定临时字符串数组到底有什么困扰你?

顺便说一句,您可以使用 System.arrayCopy 用几行代码缩短代码,而不是实现自己的循环来复制数组元素:

System.arrayCopy(s, 0, o, 0, s.length);
于 2012-09-11T13:03:53.693 回答