【问题标题】:Dealing with an ArrayStoreException处理 ArrayStoreException
【发布时间】:2012-09-04 09:33:09
【问题描述】:
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[] 数组的情况下,还有其他方法可以处理该异常吗?

【问题讨论】:

  • 对象[] o = "a;b;c".split(";"); o[0] = 42;在这里,您正在创建一个 Strings 数组,而 Object[] o = new Object[s.length];是一个对象数组。
  • 请参阅 java.lang.ArrayStoreException 类:docs.oracle.com/javase/8/docs/api/java/lang/…

标签: java arrays casting type-conversion


【解决方案1】:

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

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

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

问题是String[] 只支持Object[] 合约的部分。例如,您可以读取来自 Object[]Object 值。您还可以读取来自String[]Object 值(恰好是String 对象)。到目前为止,一切都很好。问题在于合同的另一部分。您可以将 any Object 放入 Object[]。但是您不能将 any Object 放入 String[]。因此,String[] 不应被视为 Object[] 的子类型,但 Java 规范说它是。因此我们会产生这样的后果。

(请注意,类似的情况再次出现在泛型类中,但这次解决正确List&lt;String&gt; 不是List&lt;Object&gt; 的子类型;如果你想有一个共同的超类型,你需要List&lt;?&gt;,它是只读的。这也是数组应该是这样的;但事实并非如此。而且由于向后兼容,现在为时已晚改变它。)

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

【讨论】:

【解决方案2】:

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

split 返回的数组实际上是一个String[],Java 允许您将其分配给Object[] 类型的变量。然而,它仍然是一个 String[],所以当你尝试在其中存储 String 以外的其他内容时,你会得到一个 ArrayStoreException

有关背景信息,请参阅 Java 语言规范中的 4.10.3. Subtyping among Array Types

【讨论】:

    【解决方案3】:

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

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

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

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

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

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

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

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

    2)
    首次初始化在本地使用的数组时,请使用 X[] blah = new X[...];X[] blah = {...}; 或(从 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" 因此在决定采用哪条路径时,应考虑将如何影响您的其余代码。

    我只想以关于泛型的说明结束(如之前的回答中所述)。泛型实际上同样能够让毫无戒心的编码人员感到惊讶。考虑以下代码 sn-p,(修改自 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));
        }
    }
    

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

    【讨论】:

      【解决方案4】:

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

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

      System.arrayCopy(s, 0, o, 0, s.length);
      

      【讨论】:

        猜你喜欢
        • 2012-12-26
        • 1970-01-01
        • 1970-01-01
        • 2023-04-03
        • 2021-02-28
        • 2016-11-18
        • 1970-01-01
        • 2014-03-18
        • 1970-01-01
        相关资源
        最近更新 更多