これはHeadFirst Javaからのものです:(575ページ)
この:
public <T extends Animal> void takeThing(ArrayList<T> list)
これと同じことをします:
public void takeThing(ArrayList<? extends Animal> list)
だからここに私の質問があります:それらが完全に同じであるなら、なぜ私たちは書きませんか?
public <? extends Animal> void takeThing(ArrayList<?> list)
または
public void takeThing(ArrayList<T extends Animal> list)
また、? Genericsを使用した(上記の)メソッド宣言のTの代わりに、またはClass宣言の場合は?メリットは何ですか?
の大きな違い
public <T extends Animal> void takeThing(ArrayList<T> list)
そして
public void takeThing(ArrayList<? extends Animal> list)
前者のメソッドでは、メソッド内の「T」を、指定された具象クラスとして参照できます。 2番目の方法では、これを行うことはできません。
これを説明するためのより複雑な例を次に示します。
// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
Map<T, String> names = new HashMap<T, String>();
for (T animal : list) {
names.put(animal, animal.getName()); // I assume there is a getName() method
}
return names;
}
// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
Map<Animal, String> names = new HashMap<Animal, String>();
for (Animal animal : list) {
names.put(animal, animal.getName()); // I assume there is a getName() method
}
return names;
}
最初の方法では、猫のリストを渡すと、猫をキーとするマップが得られます。 2番目のメソッドは、常に一般的なアニマルキーを持つマップを返します。
ちなみにこれは有効ではありませんJava構文:
public <? extends Animal> void takeThing(ArrayList<?> list)
この形式のジェネリックメソッド宣言を使用するには、有効なJava識別子であり、 "?"ではありません。
編集:
「?extends Type」の形式は、変数またはパラメーターの型宣言にのみ適用されます。メソッド内から「識別子」を参照できるため、ジェネリックメソッドの宣言内では「識別子はタイプを拡張しています」である必要があります。
ワイルドカードは、ジェネリックの共分散または逆分散についてです。いくつかの例を示して、これが何を意味するのかを明らかにしようと思います。
基本的に、タイプSおよびTの場合、SがTのサブタイプであり、ジェネリックタイプG<S>
はG<T>
の有効なサブタイプではないという事実に関連しています。
List<Number> someNumbers = new ArrayList<Long>(); // compile error
ワイルドカードでこれを修正できます
List<? extends Number> someNumbers = new ArrayList<Long>(); // this works
そのようなリストには何も入れることができないことに注意してください
someNumbers.add(2L); //compile error
さえ(そして多くの開発者にとってもっと驚くべき):
List<? extends Long> someLongs = new ArrayList<Long>();
someLongs.add(2L); // compile error !!!
SOは、それを詳細に議論するのに適切な場所ではないと思います。これをより詳細に説明する記事や論文をいくつか見つけようと思います。
型を型パラメーターにバインドすると、メソッドが何をすることになっているかに応じて、より強力になる可能性があります。 takeThing
が何をすることになっているのかはわかりませんが、一般に、これらの型シグネチャの1つを持つメソッドがあると想像してください。
public <T extends Animal> void foo(ArrayList<T> list);
//or
public void foo(ArrayList<? extends Animal> list);
これは、first型シグネチャでしか実行できない具体的な例です。
public <T extends Animal> void foo(ArrayList<T> list) {
list.add(list.remove(0)); // (cycle front element to the back)
}
この場合、T
は、リストからremovedされている要素がaddリストへ。
ワイルドカードは型パラメーターにバインドされていないため、そのコンテキストは追跡されないため、ワイルドカードを使用してこれを行うことはできません(「キャプチャ」を通じて追跡されますが、活用することはできません)。あなたは私が与えた別の答えでこれに関する詳細情報を得ることができます: ジェネリックのジェネリックはどのように機能しますか?
? extends T
と書くと、「T以上の特定のもの」と言います。たとえば、List<Shape>
にはShape
sのみを含めることができますが、List<? extends Shape>
にはShape
s、Circle
s、Rectangle
sなどを含めることができます。
? super T
と書くと、「T以上の一般的なものすべて」と言います。これはあまり使用されませんが、ユースケースがあります。典型的な例はコールバックです。Rectangle
をコールバックに戻したい場合は、Callback<? super Rectangle>
を使用できます。これは、Callback<Shape>
がRectangle
sも処理できるためです。
こちらが 関連するウィキペディアの記事 です。
takeThing
メソッドでlist
パラメータに要素を追加する必要がある場合、ワイルドカードバージョンはコンパイルされません。
興味深いケースは、リストに追加しておらず、両方のバージョンがコンパイルされて機能しているように見える場合です。
この場合、リストにさまざまな種類の動物を許可する場合(より柔軟)にワイルドカードバージョンを記述し、リストに固定タイプの動物(Tタイプ)が必要な場合にはパラメーターバージョンを記述します。
たとえば、_Java.util.Collection
_は次のように宣言します。
_interface Collection<E> {
...
public boolean containsAll(Collection<?> c);
...
}
_
また、次のコードがあるとします。
_Collection<Object> c = Arrays.<Object>asList(1, 2);
Collection<Integer> i = Arrays.<Integer>asList(1, 2, 3);
i.containsAll(c); //compiles and return true as expected
_
_Java.util.Collection
_が次の場合:
_interface Collection<E> {
...
public boolean containsAll(Collection<E> c);
...
}
_
上記のテストコードはコンパイルされず、Collection
APIの柔軟性が低下します。
後者のcontainsAll
の定義には、コンパイル時により多くのエラーをキャッチできるという利点があります。次に例を示します。
_Collection<String> c = Arrays.asList("1", "2");
Collection<Integer> i = Arrays.asList(1, 2, 3);
i.containsAll(c); //does not compile, the integer collection can't contain strings
_
しかし、Collection<Object> c = Arrays.<Object>asList(1, 2);
を使用して有効なテストに失敗しました
Java Genericsワイルドカードの使用法は、GET-PUT原則(IN-OUT原則とも呼ばれます)によって管理されます。これは次のように述べています。構造体から値を取得するだけの場合は「拡張」ワイルドカードを使用し、構造体に値を入れるだけの場合は「スーパー」ワイルドカードを使用し、両方を実行する場合はワイルドカードを使用しないでください。これはメソッドの戻り値の型には適用されません。 ワイルドカードを戻り値の型として使用しないでください。以下の例をご覧ください。
public static<T> void copyContainerDataValues(Container<? extends T> source, Container<? super T> destinationtion){
destination.put(source.get());
}