ジェネリックメソッドを使用する場合とワイルドカードを使用する場合


122

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番目の宣言の違いは何ですか?どちらも同じことをしているように見えますか?

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

回答:


173

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

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

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

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>andおよびList<Float>as 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

参照:


1
これは奇妙な答えです。なぜ使用する必要があるのか​​はまったく説明されていません?。`public static <T1 extends Number、T2 extends Number> void copy(List <T1> dest、List <T2> src)として書き直すことができ、この場合、何が起こっているかが明らかになります。
kan

@kan。まあそれは本当の問題です。タイプパラメータを使用して同じタイプを強制できますが、ワイルドカードを使用してそれを行うことはできません。型パラメーターに2つの異なる型を使用することは異なります。
Rohit Jain

1
@ベンツ。List型パラメーターを使用して下限を定義することはできません。List<T super Integer>無効であり、コンパイルされません。
Rohit Jain 2013

2
@ベンツ。どういたしまして:)最後に投稿したリンクをご覧になることを強くお勧めします。これが、Genericsに関する最高のリソースです。
Rohit Jain 2013

3
@ jorgen.ringen- <T extends X & Y>>複数の境界。
Rohit Jain

12

2つのSinglyLinkQueueをマージする以下のJames Goslingの第4版によるJavaプログラミングの次の例を検討してください。

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つ以上のパラメーターを関連付けるかどうかを確認してください。または、パラメータタイプを戻り値のタイプに関連付ける。答えが「いいえ」の場合、ワイルドカードで十分です。」

注:本では2番目の方法のみが示され、型パラメーター名は 'T'ではなくSです。最初の方法は本にはありません。


本の引用に賛成票を投じました、それは直接的で簡潔です
Kurapika

9

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

例えば:

public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);

ここでは、特定の基準に従ってTの一部を抽出しています。TがLong、あなたの方法が返されますLongCollection<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> srcTのサブクラスである1つの特定のクラスSになるように制約します。リストには、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. */

1
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();有効な構文ではありません。境界なしでArrayListをインスタンス化する必要があります。
Arnold Pistorius

バスケットは梨のリストである可能性があるため、上記の例ではバスケットにリンゴを追加できません。不適切な例のAFAIK。また、コンパイルもしません。
Khanna111 2016

1
@ArnoldPistoriusそれは私を混乱させました。私はArrayListのAPIドキュメントを確認しましたが、これには署名されたコンストラクタがありArrayList(Collection<? extends E> c)ます。どうしてそんなことを言ったのか説明してもらえますか?
クラピカ

@クラピカ古いJavaバージョンを使っていたのでは?コメントはほぼ3年前に投稿されました。
アーノルド

2

ワイルドカードメソッドも汎用的です-いくつかの種類の型で呼び出すことができます。

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

さらに、?フィールドを宣言する場合、構文は避けられません:

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

3
これはコメントではないのですか?
Buhake Sindi 2013

@BuhakeSindiすみません、何が不明確ですか?なぜ-1?私はそれが質問に答えると思います。
kan

2

一つ一つお答えしていきます。

ワイルドカードのような(Collection<? extends E> c);ものも一種の多態性をサポートしていると思いませんか?

いいえ。理由は、バインドされたワイルドカードに定義されたパラメータータイプがないためです。不明です。それが「知っている」のは、「封じ込め」がタイプE(定義されているもの)であることだけです。したがって、提供された値が境界タイプと一致するかどうかを検証および正当化することはできません。

そのため、ワイルドカードで多態的な振る舞いをすることは賢明ではありません。

ドキュメントは2番目の宣言を思いとどまらせ、最初の構文の使用を促進しますか?最初と2番目の宣言の違いは何ですか?両方とも同じことをしているように見えますか?

この場合、最初のオプションはT常に制限されるため、より適切でありsource、サブクラスの値(unknownの値)が確実に含まれますT

したがって、数値のすべてのリストをコピーする場合、最初のオプションは次のようになります

Collections.copy(List<Number> dest, List<? extends Number> src);

src本質的に、受け入れることができList<Double>List<Float>上部に見られるパラメータ化された型に結合が存在するように等、dest

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

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


最後の段落の意味を説明していただけますか
ベンツ2013

2番目のオプションを示すものは、あなたにそれをバインドすることを強制します......それについて詳しく説明できますか
ベンツ

<S extends T>状態Sのサブクラスでパラメータ化された型でありT、それはのサブクラスでパラメータ化された型(ワイルドカード)を必要としますT
Buhake Sindi 2013

2

ここに記載されていないもう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
    }
}

0

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

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

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

[...]

ワイルドカードを使用すると、明示的な型パラメーターを宣言するよりも明確で簡潔になるため、可能な限り優先する必要があります。

[...]

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


0

主に->ワイルドカードは、非ジェネリックメソッドのパラメーター/引数レベルでジェネリックを適用します。注意。デフォルトでgenericMethodでも実行できますが、ここでは?T自体を使用できます。

パッケージジェネリック;

public class DemoWildCard {


    public static void main(String[] args) {
        DemoWildCard obj = new DemoWildCard();

        obj.display(new Person<Integer>());
        obj.display(new Person<String>());

    }

    void display(Person<?> person) {
        //allows person of Integer,String or anything
        //This cannnot be done if we use T, because in that case we have to make this method itself generic
        System.out.println(person);
    }

}

class Person<T>{

}

SOワイルドカードには、このような特定のユースケースがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.