この理由は、Javaがどのようにジェネリックを実装しているかに基づいています。私がそれを説明するために見つけた最良の方法は、配列とジェネリックコレクションを正確に比較することです。
アン配列の例あなたがこれを行うことができますアレイと
:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
しかし、あなたがこれを行うにしようとした場合に何が起こるのでしょうか?
Number[0] = 3.14; //attempt of heap pollution
この最後の行はうまくコンパイルだろうが、あなたはこのコードを実行した場合、あなたはArrayStoreException
を得ることができます。
これは、コンパイラをだますことができますが、ランタイムタイプのシステムをだますことはできないことを意味します。配列はと呼ばれるものなので、これはそうです。です。これは実行時にJavaがこの配列が実際にはNumber[]
の参照でアクセスされる整数の配列として実際にインスタンス化されたことを知っていることを意味します。
ご覧のとおり、オブジェクトの実際のタイプが1つあります。もう1つは、アクセスに使用する参照のタイプですか?
Javaのジェネリック
を通報しますさて、Javaのジェネリック型の問題は、型情報がコンパイラによって破棄され、それが実行時に利用できないことです。このプロセスはtype erasureと呼ばれます。 Javaのようにジェネリックスを実装するのには良い理由がありますが、これは長い話です。既存のコードとのバイナリ互換性に関係しています。
ここで重要な点は、実行時に型情報がないため、ヒープ汚染が発生していないことを保証する方法がないことです。例えば
、
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts;
myNums.add(3.14); //heap polution
Javaコンパイラはコンパイル時にこれをやってからあなたを停止しない場合は方法がないため、実行時の型システムがそれを決定するために、実行時に、どちらかのあなたを停止することはできませんこのリストは整数のみのリストであると考えられていました。 Javaランタイムは、作成されたときに整数リストとして宣言されたので、必要なものをこのリストに入れることができます。
このように、Javaの設計者はあなたがコンパイラをだますことができないようにしました。コンパイラを欺くことができない場合(配列でできるように)、実行時型システムを欺くことはできません。
このように、一般的なタイプはです。です。
明らかに、これは指摘されているように多形性を妨げることになります。解決策は、共分散と反分散と呼ばれるJavaジェネリックの2つの強力な機能を使用する方法を学ぶことです。
共分散共分散あなたが構造から項目を読み取ることができますが、あなたはそれに何かを書き込むことはできませんで
。これらはすべて有効な宣言です。
List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()
そして、あなたはmyNums
から読み取ることができます:あなたは、実際のリストが含まれているものは何でもことを確認することができますので数を拡張し、すべてのものは、番号は、後に、それは(数にupcastedすることができ
Number n = myNums.get(0);
、右?)
ただし、共変な構造に何かを入れることはできません。
myNumst.add(45L);
Javaは実際のオブジェクトの実際のタイプが保証できないため、これは許可されません。これは、Numberを拡張するものであれば何でも構いませんが、コンパイラは確実ではありません。あなたは読むことができますが、書くことはできません。contravarianceで
Contravariance
あなたは反対のことを行うことができます。物事を一般的な構造にすることはできますが、そこから読み出すことはできません。この場合
List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");
List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);
番号は共通の祖先としてオブジェクトを持っている基本的にあるため、オブジェクトの実際の性質は、オブジェクトのリストである、とcontravarianceを通じて、あなたはそれに数字を置くことができます。したがって、すべての数値はオブジェクトなので、これは有効です。
しかし、あなたが数値を取得すると仮定して、この反変種構造から何かを安全に読み取ることはできません。
Number myNum = myNums.get(0); //compiler-error
ご覧のとおり、コンパイラがこの行を書くことを許可した場合、実行時にClassCastExceptionが発生します。あなただけの、contravarianceを使用した構造のうち、一般的な値を取るようにしようとするとき、あなただけの構造に一般的な値を入れて、正確なジェネリックを使用する際にこのよう
取得/入れ原理
、共分散を使用両方を行う場合は入力してください。
私が持っている最良の例は、ある種の数字をあるリストから別のリストにコピーする次のものです。これは、このような場合のために働く共変性と反変性の力に
public static void copy(List<? extends Number> source, List<? super Number> destiny) {
for(Number number : source) {
destiny.add(number);
}
}
ありがとう:
List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();
copy(myInts, myObjs);
copy(myDoubles, myObjs);
'列挙型のCollection'が代わりに' 'EnumSet.allOf(Module.class)を使用するようにします。 – viktor
問題の解決策があれば、実際に投票できるようにコメントに比べて回答を投稿しているはずです。しかし、他の答えはより簡単な解を与える。 –