2012-10-19 23 views
5

これらのような一般的な多型をいつ使うべきですか? 人々はメソッド定義にList<? super Dog>またはList<? extends Animal>を使用するときに私は理解して:多形ジェネリック型の理由と種類

1. List<? super Dog> list = new ArrayList<Animal>(); 
2. List<? extends Animal> list = new ArrayList<Dog>(); 
3. List<?> list = new ArrayList<Dog>(); 

誰もが注意

List<? super Dog> list = new ArrayList<Dog>(); 
List<? extends Animal> list = new ArrayList<Animal>(); 

のようなものを使用します。しかし、私が理解できないのは、多型ジェネリック型オブジェクトの作成です。

+0

チェックこれをhttp://stackoverflow.com/questions/2745265/is-listdog-a-subclass:これは、このような場合のために働く共変性と反変性の力に

public static void copy(List<? extends Number> source, List<? super Number> destiny) { for(Number number : source) { destiny.add(number); } } 

感謝-of-listanimal-why-arent-javas-generics-implicit – NPKR

答えて

11

この理由は、Javaがジェネリックを実装する方法に基づいています。

アン配列の例は配列で

あなたはこれを行うにしようと何が起こるか、この(配列は共変です)

Integer[] myInts = {1,2,3,4}; 
Number[] myNumber = myInts; 

を行うことができますが?

Number[0] = 3.14; //attempt of heap pollution 

この最後の行はうまくコンパイルだろうが、あなたはこのコードを実行した場合、あなたはArrayStoreExceptionを得ることができます。なぜなら、あなたは数値配列を通してアクセスされているかどうかに関わらず、整数配列にdoubleを入れることを試みているからです。

これは、コンパイラをだますことができますが、ランタイムタイプのシステムをだますことはできないことを意味します。配列はと呼ばれるものなので、これはそうです。です。これは実行時にJavaがこの配列が実際にはNumber[]の参照でアクセスされる整数の配列として実際にインスタンス化されたことを知っていることを意味します。

ご覧のとおり、オブジェクトの実際のタイプと、アクセスに使用する参照のタイプの違いがあります。

Javaのジェネリック

を通報しますさて、Javaのジェネリック型の問題は、型情報がコンパイラによって破棄され、それが実行時に利用できないことです。このプロセスはtype erasureと呼ばれます。 Javaのようにジェネリックスを実装するのには良い理由がありますが、これは長い話です。既存のコードとのバイナリ互換性に関係しています。

ここで重要な点は、実行時に型情報がないため、ヒープ汚染をコミットしていないことを保証する方法がないことです。例えば

List<Integer> myInts = new ArrayList<Integer>(); 
myInts.add(1); 
myInts.add(2); 

List<Number> myNums = myInts; //compiler error 
myNums.add(3.14); //heap polution 

Javaコンパイラがこれをやってからあなたを停止しない場合は、実行時の型システムは方法がないため、実行時に、このリストはあったと判断するために、どちらかのあなたを停止することはできません整数のリストだけであるはずです。 Javaランタイムは、作成されたときに整数リストとして宣言されたので、必要なものをこのリストに入れることができます。

このように、Javaの設計者はあなたがコンパイラをだますことができないようにしました。コンパイラを欺くことができない場合(配列でできるように)、実行時型システムを欺くことはできません。

このように、一般的なタイプはです。です。

明らかに、これは多型を妨げることになります。次の例を考えてみましょう:

static long sum(Number[] numbers) { 
    long summation = 0; 
    for(Number number : numbers) { 
     summation += number.longValue(); 
    } 
    return summation; 
} 

今、あなたはこのようにそれを使用することができます。

Integer[] myInts = {1,2,3,4,5}; 
Long[] myLongs = {1L, 2L, 3L, 4L, 5L}; 
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0}; 

System.out.println(sum(myInts)); 
System.out.println(sum(myLongs)); 
System.out.println(sum(myDoubles)); 

しかし、あなたはジェネリックコレクションと同じコードを実装しようとした場合、あなたは成功しません。

static long sum(List<Number> numbers) { 
    long summation = 0; 
    for(Number number : numbers) { 
     summation += number.longValue(); 
    } 
    return summation; 
} 

しようとすると、コンパイラエラーが発生します...

List<Integer> myInts = asList(1,2,3,4,5); 
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L); 
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0); 

System.out.println(sum(myInts)); //compiler error 
System.out.println(sum(myLongs)); //compiler error 
System.out.println(sum(myDoubles)); //compiler error 

解決策は、共分散と反分散と呼ばれる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); //compiler error 

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を使用した構造のうち、一般的な値を取るようにしようとするとき、あなただけの構造に一般的な値を入れて、正確なジェネリックを使用する際にこのよう

取得/入れ原理

、共分散を使用両方を行う場合は入力してください。

私が持っている最良の例は、ある種の数字をあるリストから別のリストにコピーする次のものです。 のアイテムをソースから取得し、のアイテムを運命に入れます。

List<Integer> myInts = asList(1,2,3,4); 
List<Double> myDoubles = asList(3.14, 6.28); 
List<Object> myObjs = new ArrayList<Object>(); 

copy(myInts, myObjs); 
copy(myDoubles, myObjs); 
+1

エドウィンありがとう。これはすばらしい説明です – John

0

彼らは使用します。

  1. 彼らはDogのスーパークラスであるオブジェクトのリストが必要な場合。例えば、Animalのようになります。

  2. 動物(サブクラスAnimal)のリストが必要な場合。例えばDogのようになります。

  3. オブジェクトのリストが必要な場合は、ピリオド。これは偶然、例えば犬のリストになることがあります。

+0

あなたはさらに説明できますか? – John

関連する問題