OracleDocGenericMethod からジェネリックメソッドについて読んでいます。ワイルドカードを使用するタイミングと汎用メソッドを使用するタイミングを比較するときに、比較についてかなり混乱しています。文書から引用。
interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); }
代わりにここでジェネリックメソッドを使用することもできます。
interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // Hey, type variables can have bounds too! }
[…]これは、型引数がポリモーフィズムに使用されていることを示しています。唯一の効果は、さまざまな実際の引数タイプを異なる呼び出しサイトで使用できるようにすることです。その場合は、ワイルドカードを使用する必要があります。ワイルドカードは、柔軟なサブタイピングをサポートするように設計されています。これは、ここで表現しようとしているものです。
(Collection<? extends E> c);
のようなワイルドカードも一種のポリモーフィズムをサポートしていると思いませんか?では、なぜ一般的なメソッドの使用はこれで良くないと考えられますか
続けて、それは述べています、
ジェネリックメソッドを使用すると、型パラメーターを使用して、メソッドおよび/またはその戻り型に対する1つ以上の引数の型間の依存関係を表現できます。そのような依存関係がない場合は、汎用メソッドを使用しないでください。
これは何を意味するのでしょうか?
彼らは例を提示しました
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ... }
[…]
ワイルドカードをまったく使用せずに、このメソッドの署名を別の方法で作成することもできます。
class Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }
この文書は2番目の宣言を推奨せず、最初の構文の使用を促進しますか?最初の宣言と2番目の宣言の違いは何ですか?両方が同じことをしているようです?
誰かがこのエリアに光を当てることができますか。
ワイルドカードと型パラメーターが同じことを行う特定の場所があります。しかし、型パラメーターを使用する必要がある特定の場所もあります。
メソッドを例にとると、copy()
メソッドに渡されるsrc
およびdest
リストが同じパラメーター化された型であることを確認したい場合、次のような型パラメーターを使用して実行できます。
public static <T extends Number> void copy(List<T> dest, List<T> src)
ここでは、dest
とsrc
の両方がList
に対して同じパラメーター化された型を持っていることが保証されます。したがって、src
からdest
に要素をコピーしても安全です。
ただし、ワイルドカードを使用するようにメソッドを変更する場合:
public static void copy(List<? extends Number> dest, List<? extends Number> src)
期待どおりに動作しません。 2番目のケースでは、List<Integer>
およびList<Float>
をdest
およびsrc
として渡すことができます。したがって、要素をsrc
からdest
に移動すると、タイプセーフではなくなります。そのような種類のリレーションが必要ない場合は、型パラメーターをまったく使用しないでください。
ワイルドカードと型パラメーターの使用のその他の違いは次のとおりです。
ワイルドカードは上限と下限の両方をサポートし、型パラメーターは上限のみをサポートします。したがって、List
型のInteger
またはスーパークラスをとるメソッドを定義する場合は、次のようにできます。
public void print(List<? super Integer> list) // OK
ただし、typeパラメーターは使用できません。
public <T super Integer> void print(List<T> list) // Won't compile
参照:
最初の質問:パラメータの型とメソッドの戻り値の型の間に関係がある場合、ジェネリックを使用することを意味します。
例えば:
public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);
ここでは、特定の基準に従ってTの一部を抽出しています。 TがLong
の場合、メソッドはLong
およびCollection<Long>
を返します。実際の戻り値の型はパラメータの型に依存するため、ジェネリック型を使用すると便利です。
そうでない場合は、ワイルドカードタイプを使用できます。
public int count(Collection<?> items);
public boolean containsDuplicate(Collection<?> items);
この2つの例では、コレクション内のアイテムのタイプが何であれ、返されるタイプはint
とboolean
になります。
あなたの例では:
interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
これら2つの関数は、コレクション内のアイテムのタイプが何であれ、ブール値を返します。 2番目のケースでは、Eのサブクラスのインスタンスに制限されます。
2番目の質問:
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src) {
...
}
この最初のコードにより、異種のList<? extends T> src
をパラメーターとして渡すことができます。このリストには、すべてが基本クラスTを拡張する限り、異なるクラスの複数の要素を含めることができます。
あなたが持っていた場合:
interface Fruit{}
そして
class Apple implements Fruit{}
class Pear implements Fruit{}
class Tomato implements Fruit{}
あなたはできる
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>();
Collections.copy(fridge, basket);// works
一方
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src) {
...
}
List<S> src
をTのサブクラスである特定のクラスSに制限します。リストには、Tを実装していても、1つのクラス(この場合はS)の要素のみを含めることができます。私の前の例を使用することはできませんが、次のことができます。
List<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();
Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
2つのSinglyLinkQueueをマージする、下のJames Gosling第4版によるJava Programmingの次の例を検討してください。
public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
// merge s element into d
}
public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
// merge s element into d
}
上記の方法は両方とも同じ機能を備えています。どちらが好ましいですか?答えは2番目です。著者自身の言葉で:
「一般的に、ワイルドカードを使用したコードは複数の型パラメーターを使用したコードよりも読みやすいため、可能であればワイルドカードを使用します。型変数が必要かどうかを判断する場合、以上のパラメータ、またはパラメータタイプと戻り値のタイプを関連付けます。答えが「いいえ」の場合、ワイルドカードで十分です。 "
注:本では、2番目のメソッドのみが指定されており、タイプパラメーター名は 'T'ではなくSです。最初の方法は本にありません。
私はあなたの質問に一つ一つ答えようとします。
(Collection<? extends E> c);
のようなワイルドカードも一種のポリモーフィズムをサポートしていると思いませんか?
いいえ。理由は、bounded wildcardには定義済みのパラメータタイプがないためです。不明です。 「知っている」のは、「包含」のタイプがE
(定義されているもの)であることだけです。そのため、指定された値が境界型と一致するかどうかを検証および正当化することはできません。
したがって、ワイルドカードで多態的な動作をすることは賢明ではありません。
この文書は2番目の宣言を推奨せず、最初の構文の使用を促進しますか?最初の宣言と2番目の宣言の違いは何ですか?両方が同じことをしているようです?
この場合、T
には常に境界があり、source
にはT
をサブクラス化する(未知の)値が確実に含まれるため、最初のオプションの方が適しています。
したがって、すべての数値のリストをコピーする場合、最初のオプションは
Collections.copy(List<Number> dest, List<? extends Number> src);
src
にあるパラメータ化された型には上限があるため、dest
は基本的にList<Double>
、List<Float>
などを受け入れることができます。
2番目のオプションでは、コピーするすべてのタイプに対してS
をバインドするように強制されます。
//For double
Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.
//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.
S
は、バインドが必要なパラメータ化された型です。
これがお役に立てば幸いです。
ここにリストされていないもう1つの違い。
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // correct
}
}
ただし、次の場合、コンパイル時エラーが発生します。
static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
for (T o : a) {
c.add(o); // compile time error
}
}
ワイルドカードメソッドもジェネリックです-ある範囲のタイプで呼び出すことができます。
<T>
構文は、型変数名を定義します。型変数に用途がある場合(たとえば、メソッドの実装や他の型の制約として)、名前を付けるのが理にかなっています。そうでない場合は、?
を匿名変数として使用できます。だから、単なるショートカットのように見えます。
さらに、フィールドを宣言するとき、?
構文は回避できません。
class NumberContainer
{
Set<? extends Number> numbers;
}
私が理解している限り、ワイルドカードが厳密に必要なユースケースは1つだけです(つまり、明示的な型パラメーターを使用して表現できないものを表現できます)。これは、下限を指定する必要がある場合です。
ただし、それとは別に、ワイルドカードは、あなたが言及するドキュメントの次のステートメントで説明されているように、より簡潔なコードを書くのに役立ちます。
ジェネリックメソッドを使用すると、型パラメーターを使用して、メソッドおよび/または戻り値型に対する1つ以上の引数の型間の依存関係を表現できます。そのような依存関係がない場合は、汎用メソッドを使用しないでください。
[...]
ワイルドカードの使用は、明示的な型パラメーターを宣言するよりも明確で簡潔なので、可能な限り優先されるべきです。
[...]
ワイルドカードには、フィールドのタイプ、ローカル変数、配列として、メソッドシグネチャの外部で使用できるという利点もあります。