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[]しかサポートしていないことです。たとえば、から値を読み取ることができます。また、から値(たまたまオブジェクト)を読み取ることもできます。ここまでは順調ですね。問題は契約の他の部分にあります。に任意を入れることができます。ただし、に入れることはできませ。したがって、のサブタイプと見なすべきではありませんが、Java仕様ではそうです。したがって、このような結果になります。Object[] ObjectObject[] 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は実際にはString[]であり、Java ではそれをタイプ の変数に割り当てることができますObject[]。ただし、これは実際にString[]は であるため、 以外のものを格納しようとするStringArrayStoreException.

背景情報については、4.10.3 を参照してください。Java 言語仕様の配列型のサブタイプ。

于 2012-09-11T12:44:01.670 に答える
1

これは、何ヶ月も前に Java 開発者が行った取引の結果です。奇妙に思えるかもしれませんが、この機能はArrays.sort( で呼び出されることもあります) などの多くのメソッドにとって重要ですCollections.sort。基本的に、Object[] をパラメーターとして受け取るメソッドは、X[] (X は Object のサブクラス) がサブタイプと見なされない場合、意図したとおりに実行されなくなります。たとえば、配列が特定の状況下で読み取り専用になるように作り直された可能性はありますが、その場合、問題は「いつ?」になります。

一方では、引数としてメソッドに渡された配列を読み取り専用にすると、コーダーがその場で変更を行う能力が妨げられる可能性があります。一方、配列が引数として渡される場合に例外を作成しても、コーダーは、整数配列が呼び出し元によって渡されたものである場合に文字列を格納するなど、不正な変更を行うことができます。

しかし、「(たとえば) Integer[] は Object[] のサブタイプではない」と言う結果は、Object[] と Integer[] に対して別のメソッドを作成しなければならない危機です。このようなロジックの拡張により、String[]、Comparable[] などに対して個別のメソッドを作成する必要があるとさらに言えます。すべてのタイプの配列には、それらのメソッドがまったく同じであったとしても、個別のメソッドが必要になります。

これはまさに、ポリモーフィズムが存在する状況です。

ただし、ここでポリモーフィズムを許可すると、残念ながら、配列に値を不正に格納しようとする試みが許可され、ArrayStoreExceptionそのようなインスタンスが発生した場合は がスローされます。ただし、これは支払うべき小さな代償であり、ArrayIndexOutOfBoundsException.

ArrayStoreExceptionほとんどの場合、2 つの方法で簡単に防ぐことができます (ただし、他の人の行動を制御することはできません)。

1) 実際のコンポーネント
の型を知らずにオブジェクトを配列に格納しようとしないでください。操作している配列がメソッドに渡されたとき、それがどこから来ているかは必ずしもわからないため、コンポーネント タイプのクラスが final (つまり、サブクラスがない) でない限り、安全であると想定することはできません。

上記の質問のように、配列がメソッドから返される場合は、メソッドを理解してください。実際の型が戻り型のサブクラスである可能性はありますか? その場合、これを考慮に入れる必要があります。

2)
ローカルで動作する配列を最初に初期化するときは、フォームX[] blah = new X[...];orX[] blah = {...};または (Java 10 以降) を使用しvar blah = new X[...];ます。次に、この配列に X 以外の値を格納しようとすると、コンパイラ エラーが発生します。XはY[] blah = new 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メソッドを実装するなど、他のオプションもあります。一時的な文字列配列で実際に何が気になるのかわかりませんが?

ところで、配列要素をコピーする独自のループを実装する代わりに、System.arrayCopyを使用して、コードを数行で短縮できます。

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