チェックされていない型キャストを介してJavaで汎用配列を作成する 質問する

チェックされていない型キャストを介してJavaで汎用配列を作成する 質問する

ジェネリッククラスがある場合Foo<Bar>、次のように配列を作成することはできません。

Bar[] bars = new Bar[];

(これにより、「Bar の汎用配列を作成できません」というエラーが発生します)。

しかし、ディモ414に対する答えとしてこの質問 (Java の使い方: 汎用配列の作成)、次のことができます。

Bar[] bars = (Bar[]) new Object[];

(これにより、「型の安全性: Object[] から Bar[] へのチェックされていないキャスト」という警告のみが生成される)。

コメントに返信するディモ414の回答では、この構造を使用すると特定の状況で問題が発生する可能性があると主張する人もいれば、配列への唯一の参照が でありbars、これがすでに目的の型であるため問題ないと主張する人もいます。

どのような場合にこれが問題ないのか、またどのような場合に問題を引き起こすのか、少し混乱しています。新規アカウントそしてアーロン・マクデイドたとえば、これらは互いに直接矛盾しているように見えます。残念ながら、元の質問のコメント ストリームは単に「なぜこれが「もはや正しくない」のですか?」という未回答で終わっているので、新しい質問を作成することにしました。

bars-array に 型のエントリのみが含まれる場合Bar、配列またはそのエントリを使用するときに実行時の問題が発生する可能性がありますか? または、唯一の危険は、実行時に配列を技術的に別のもの ( などString[]) にキャストして、 以外の型の値で埋めることができることですかBar?

代わりにを使用できることはわかっていますArray.newInstance(...)が、たとえば GWT では -optionnewInstance(...)が使用できないため、上記の型キャスト構造に特に興味があります。

ベストアンサー1

質問の中で私の名前が挙がっていたので、私も意見を述べさせていただきます。

基本的に、この配列変数をクラスの外部に公開しなくても問題は発生しません。(ラスベガスで起こったことはラスベガスに留まるようなものです。)

配列の実際の実行時型は です。したがって、は のサブタイプではないため(でない限り)、Object[]これを 型の変数に入れることは事実上「嘘」です。ただし、 はクラス内で に消去されるため、この嘘はクラス内にとどまっていれば問題ありません。( の下限は、この質問ではです。 の下限が別の値である場合は、この説明で のすべての出現箇所をその下限値に置き換えてください。) ただし、この嘘が何らかの形で外部に公開されると (最も単純な例は、変数を型として直接返すことです)、問題が発生します。Bar[]Object[]Bar[]ObjectBarBarObjectBarObjectBarObjectbarsBar[]

実際に何が起こっているかを理解するには、ジェネリックのあるコードとないコードを見ることが有益です。ジェネリックのプログラムは、ジェネリックを削除し、適切な場所にキャストを挿入するだけで、同等の非ジェネリックプログラムに書き直すことができます。この変換は型消去

Foo<Bar>配列内の特定の要素を取得および設定するメソッドと、配列全体を取得するメソッドを備えた の単純な実装を検討します。

class Foo<Bar> {
    Bar[] bars = (Bar[])new Object[5];
    public Bar get(int i) {
        return bars[i];
    }
    public void set(int i, Bar x) {
        bars[i] = x;
    }
    public Bar[] getArray() {
        return bars;
    }
}

// in some method somewhere:
Foo<String> foo = new Foo<String>();
foo.set(2, "hello");
String other = foo.get(3);
String[] allStrings = foo.getArray();

型消去後、次のようになります。

class Foo {
    Object[] bars = new Object[5];
    public Object get(int i) {
        return bars[i];
    }
    public void set(int i, Object x) {
        bars[i] = x;
    }
    public Object[] getArray() {
        return bars;
    }
}

// in some method somewhere:
Foo foo = new Foo();
foo.set(2, "hello");
String other = (String)foo.get(3);
String[] allStrings = (String[])foo.getArray();

したがって、クラス内にキャストはもうありません。ただし、呼び出しコードにはキャストがあります -- 1 つの要素を取得するときと、配列全体を取得するときです。1 つの要素を取得するキャストは失敗しないはずです。配列に入れることができるのは だけでありBar、取り出すことができるのも だけだからですBar。ただし、配列全体を取得するときのキャストは失敗します。これは、配列の実際の実行時型が であるためですObject[]

非ジェネリックで記述すると、何が起こっているのか、そして問題がはるかに明らかになります。特に厄介なのは、キャストの失敗はジェネリックでキャストを記述したクラスでは発生せず、クラスを使用する他の人のコードで発生することです。そして、他の人のコードは完全に安全で無害です。また、ジェネリック コードでキャストを実行したときにも発生せず、後で誰かが を呼び出したときにgetArray()警告なしに発生します。

このメソッドがなければgetArray()、このクラスは安全です。このメソッドがあると、安全ではありません。どのような特性が安全でないのでしょうか。これは、先ほど作った「嘘」に依存するbarstype として返されますBar[]。この嘘は真実ではないので、問題が発生します。メソッドが代わりに type として配列を返した場合Object[]、「嘘」に依存しないため、安全になります。

このようなキャストは行わないよう勧める人もいるでしょう。なぜなら、チェックされていないキャストがあった元の場所ではなく、上記のように予期しない場所でキャスト例外が発生するからです。コンパイラは、それがgetArray()安全でないことを警告しません (コンパイラの観点からは、指定された型を考えると安全であるため)。したがって、この落とし穴に注意し、安全でない方法で使用しないようにするのはプログラマー次第です。

しかし、これは実際には大きな問題ではないと私は主張します。適切に設計された API は、内部インスタンス変数を外部に公開することはありません。(内容を配列として返すメソッドがあったとしても、内部変数を直接返すことはなく、外部のコードが配列を直接変更するのを防ぐためにコピーします。) したがって、getArray()いずれにしても、このように実装されるメソッドはありません。

おすすめ記事