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[]
配列を作成せずにその例外に対処する他の方法はありますか?
Java配列はオブジェクトでもあります。
subtypeのオブジェクトをsupertypeの変数に入れることができます。たとえば、String
オブジェクトをObject
変数に入れることができます。
残念なことに、Javaの配列定義は壊れています。String[]
はObject[]
のサブタイプと見なされますが、それはwrong!ですより詳細な説明は「共分散と反分散」について読みますが、本質はこれです:サブタイプがスーパータイプのすべての義務を満たす場合のみ、タイプは別のタイプのサブタイプと見なされるべきです。スーパータイプオブジェクトではなくサブタイプオブジェクトを取得する場合、スーパータイプコントラクトと矛盾する動作を期待しないでください。
問題は、String[]
はObject[]
コントラクトのpartのみをサポートすることです。たとえば、読み取りObject[]
のObject
値を使用できます。また、String[]
から読み取りObject
値(たぶんString
オブジェクト)を使用することもできます。ここまでは順調ですね。問題は契約の他の部分にあります。 anyObject
をObject[]
に入れることができます。ただし、anyObject
をString[]
に入れることはできません。したがって、String[]
はObject[]
のサブタイプと見なされるべきではありませんが、Java仕様はそうであると述べています。したがって、このような結果になります。
(同様の状況がジェネリッククラスでも再び発生したことに注意してください。ただし、今回は解決されました正しく。List<String>
はnotList<Object>
のサブタイプです;そして、これらに共通のスーパータイプが必要な場合は、読み取り専用であるList<?>
が必要です。これは、配列でも同じです;しかし、そうではありません。変更するには遅すぎます。)
最初の例では、String.split
関数がString[]
オブジェクトを作成します。 Object[]
変数に入れることができますが、オブジェクトはString[]
のままです。これがInteger
値を拒否する理由です。新しいObjects[]
配列を作成し、値をコピーする必要があります。 System.arraycopy
関数を使用してデータをコピーできますが、新しい配列の作成を避けることはできません。
いいえ、split
が返す配列のコピーを避ける方法はありません。
split
が返す配列は、実際はString[]
であり、Javaを使用すると、Object[]
型の変数に割り当てることができます。ただし、String[]
なので、String
以外の何かを格納しようとすると、ArrayStoreException
が返されます。
背景情報については、 4.10.3を参照してください。 Java Language Specification。の配列タイプ 間のサブタイプ。
これは、多くの月前に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[...];
または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"であるため、どのパスを取るかを決定する際に、残りのコードにどのように影響するかを考慮する必要があります。
ジェネリックに関するメモで終わりたいと思います(以前の回答で述べたように)。ジェネリックは、実際、疑うことを知らないコーダーを驚かすことができます。次のコードスニペットを考慮してください( 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
もちろん、Object配列を直接返す独自のsplitメソッドを実装するなど、他のオプションもあります。一時的な文字列配列で実際に何があなたを悩ますのか分かりませんか?
ところで、配列要素をコピーする独自のループを実装する代わりに、System.arrayCopyを使用して、数行でコードを短縮できます。
System.arrayCopy(s, 0, o, 0, s.length);