web-dev-qa-db-ja.com

一般的な方法を使用する場合とワイルドカードを使用する場合

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番目の宣言の違いは何ですか?両方が同じことをしているようです?

誰かがこのエリアに光を当てることができますか。

110
benz

ワイルドカードと型パラメーターが同じことを行う特定の場所があります。しかし、型パラメーターを使用する必要がある特定の場所もあります。

  1. 異なるタイプのメソッド引数に何らかの関係を強制したい場合、ワイルドカードではできません。タイプパラメーターを使用する必要があります。

メソッドを例にとると、copy()メソッドに渡されるsrcおよびdestリストが同じパラメーター化された型であることを確認したい場合、次のような型パラメーターを使用して実行できます。

public static <T extends Number> void copy(List<T> dest, List<T> src)

ここでは、destsrcの両方がListに対して同じパラメーター化された型を持っていることが保証されます。したがって、srcからdestに要素をコピーしても安全です。

ただし、ワイルドカードを使用するようにメソッドを変更する場合:

public static void copy(List<? extends Number> dest, List<? extends Number> src)

期待どおりに動作しません。 2番目のケースでは、List<Integer>およびList<Float>destおよびsrcとして渡すことができます。したがって、要素をsrcからdestに移動すると、タイプセーフではなくなります。そのような種類のリレーションが必要ない場合は、型パラメーターをまったく使用しないでください。

ワイルドカードと型パラメーターの使用のその他の違いは次のとおりです。

  • パラメーター化された型引数が1つしかない場合は、ワイルドカードを使用できますが、型パラメーターも機能します。
  • 型パラメーターは複数の境界をサポートしていますが、ワイルドカードはサポートしていません。
  • ワイルドカードは上限と下限の両方をサポートし、型パラメーターは上限のみをサポートします。したがって、List型のIntegerまたはスーパークラスをとるメソッドを定義する場合は、次のようにできます。

    public void print(List<? super Integer> list)  // OK
    

    ただし、typeパラメーターは使用できません。

     public <T super Integer> void print(List<T> list)  // Won't compile
    

参照:

156
Rohit Jain

最初の質問:パラメータの型とメソッドの戻り値の型の間に関係がある場合、ジェネリックを使用することを意味します。

例えば:

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つの例では、コレクション内のアイテムのタイプが何であれ、返されるタイプはintbooleanになります。

あなたの例では:

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です。最初の方法は本にありません。

10
chammu

私はあなたの質問に一つ一つ答えようとします。

(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は、バインドが必要なパラメータ化された型です。

これがお役に立てば幸いです。

2
Buhake Sindi

ここにリストされていないもう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
    }
}
2
Vivek Kumar

ワイルドカードメソッドもジェネリックです-ある範囲のタイプで呼び出すことができます。

<T>構文は、型変数名を定義します。型変数に用途がある場合(たとえば、メソッドの実装や他の型の制約として)、名前を付けるのが理にかなっています。そうでない場合は、?を匿名変数として使用できます。だから、単なるショートカットのように見えます。

さらに、フィールドを宣言するとき、?構文は回避できません。

class NumberContainer
{
 Set<? extends Number> numbers;
}
2
kan

私が理解している限り、ワイルドカードが厳密に必要なユースケースは1つだけです(つまり、明示的な型パラメーターを使用して表現できないものを表現できます)。これは、下限を指定する必要がある場合です。

ただし、それとは別に、ワイルドカードは、あなたが言及するドキュメントの次のステートメントで説明されているように、より簡潔なコードを書くのに役立ちます。

ジェネリックメソッドを使用すると、型パラメーターを使用して、メソッドおよび/または戻り値型に対する1つ以上の引数の型間の依存関係を表現できます。そのような依存関係がない場合は、汎用メソッドを使用しないでください。

[...]

ワイルドカードの使用は、明示的な型パラメーターを宣言するよりも明確で簡潔なので、可能な限り優先されるべきです。

[...]

ワイルドカードには、フィールドのタイプ、ローカル変数、配列として、メソッドシグネチャの外部で使用できるという利点もあります。

0