Java:制限付きワイルドカードまたは制限付き型パラメーター?


82

最近、私はこの記事を読みました:http//download.oracle.com/javase/tutorial/extra/generics/wildcards.html

私の質問は、次のようなメソッドを作成する代わりに、次のようになります。

public void drawAll(List<? extends Shape> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

このようなメソッドを作成できますが、正常に機能します。

public <T extends Shape> void drawAll(List<T> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

どちらの方法を使用すればよいですか?この場合、ワイルドカードは役に立ちますか?


?省略表記です。内部的にコンパイラはそれをtypeパラメータに置き換えます。コンパイラエラーが発生すると、?の代わりにサロゲートタイプパラメータが表示されます。
評判の悪い2010

9
訂正:私の前のコメントは間違っています。ワイルドカードは思ったより洗練されています。
評判の悪い2010

@irreputableは確かにもっと複雑ですが、typeパラメーターに置き換えることについてのあなたのポイントは完全に有効です。それはあなたが宣言できないものにすぎません。
ユージーン

私たちが見ればちょうど彼らがそうであるようにこれらの2つの方法-何の違いやスタイルだけの問題ではありません。(あなたには、いくつかのより一般的なパラメータを追加した場合)それ以外の場合は、物事は変化します
ユージン

回答:


133

それはあなた何をする必要があるかに依存します。次のようなことをしたい場合は、有界型パラメーターを使用する必要があります。

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

ここでは、持っているList<T> shapesT shape、それゆえ我々は安全にすることができますshapes.add(shape)。宣言されている場合はList<? extends Shape>、安全に行うことはできませんadd(とが存在する可能性があるList<Square>ためCircle)。

したがって、制限付き型パラメーターに名前を付けることで、ジェネリックメソッドの他の場所でそれを使用するオプションがあります。もちろん、この情報は必ずしも必要ではないため、タイプ(たとえば、drawAll)についてそれほど詳しく知る必要がない場合は、ワイルドカードだけで十分です。

境界タイプパラメータを再度参照しない場合でも、複数の境界がある場合は、境界タイプパラメータが必要です。これは、AngelikaLangerのJavaGenericsFAQからの引用です。

ワイルドカードバインドとタイプパラメータバインドの違いは何ですか?

ワイルドカードには1つの境界しかありませんが、型パラメーターには複数の境界を含めることができます。ワイルドカードには下限または上限を設定できますが、型パラメーターの下限などはありません。

ワイルドカードの境界と型パラメーターの境界は、どちらも境界と呼ばれ、構文が部分的に類似しているため、混同されることがよくあります。[…]

構文

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

ワイルドカードには、下限または上限のいずれかの境界を1つだけ含めることができます。ワイルドカード境界のリストは許可されていません。

対照的に、型パラメーターにはいくつかの境界がありますが、型パラメーターの下限などはありません。

効果的なJava2nd Edition、アイテム28からの引用:APIの柔軟性を高めるために、制限付きワイルドカードを使用します

最大限の柔軟性を得るには、プロデューサーまたはコンシューマーを表す入力パラメーターにワイルドカードタイプを使用します。[…] PECSは生産extendssuper- 、消費者-の略です-[…]

戻り値の型としてワイルドカード型を使用しないでください。ユーザーに追加の柔軟性を提供するのではなく、クライアントコードでワイルドカードタイプを使用するように強制します。適切に使用されると、ワイルドカードタイプはクラスのユーザーにはほとんど見えなくなります。それらは、メソッドに、受け入れるべきパラメーターを受け入れさせ、拒否すべきパラメーターを拒否させます。クラスのユーザーがワイルドカードタイプについて考える必要がある場合は、クラスのAPIに問題がある可能性があります

PECSの原則を適用するaddIfPrettyと、次のように記述して、例に戻り、より柔軟にすることができます。

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

今、私たちはできるaddIfPretty、と言うCircleには、List<Object>。これは明らかにタイプセーフですが、元の宣言はそれを可能にするほど柔軟ではありませんでした。

関連する質問


概要

  • 制限付きタイプのパラメーター/ワイルドカードを使用してください。APIの柔軟性が向上します。
  • 型に複数のパラメーターが必要な場合は、制限付き型パラメーターを使用する以外に選択肢はありません。
  • タイプに下限が必要な場合は、制限付きワイルドカードを使用する以外に選択肢はありません。
  • 「生産者」には上限があり、「消費者」には下限があります
  • 戻り値の型にワイルドカードを使用しないでください

どうもありがとうございました、あなたの説明は非常に明確
Tony Le

1
@Tony:この本には、制限がない場合にワイルドカードと型パラメーターのどちらを選択するかについての短い説明もあります。基本的に、typeパラメーターがメソッド宣言に1回だけ現れる場合は、ワイルドカードを使用してください。参照してくださいreverse(List<?>)JLSから例java.sun.com/docs/books/jls/third_edition/html/...を
polygenelubricants

次のコードでUnsupportedOperationExceptionが発生します。<code> public static <T extends Number> void add(List <?super T> list、T num){list.add(num); } </ code>
Samra 2013

1
また?、メソッドでdoWork(? type)そのパラメータを使用できないため、のようにメソッドパラメータとして渡すことはできません。typeパラメータを使用する必要があります。
Tomasz Mularczyk 2016年

1
@VivekVardhanワイルドカードを使用してそのようなメソッドを作成することはできません。それがポイントです。どちらを使用するかを決定するとき、これは説明する最良の情報源の1つです。
ユージーン

6

あなたの例では、他の場所ではTを使用しないため、実際にTを使用する必要はありません。

しかし、あなたが次のようなことをした場合:

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

または、polygenlubricantsが言ったように、リスト内のタイプパラメータを別のタイプパラメータと一致させたい場合:

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

最初の例では、Shapeの子をとる可能性のある関数に結果を渡すことができるため、Shapeだけを返すよりも、型の安全性が少し高くなります。たとえば、aList<Square>をmyメソッドに渡してから、結果のSquareをSquaresのみを受け取るメソッドに渡すことができます。'?'を使用した場合 結果のシェイプを正方形にキャストする必要がありますが、これはタイプセーフではありません。

2番目の例では、両方のリストに同じ型パラメーターがあることを確認し(「?」はそれぞれ異なるため、「?」では実行できません)、両方のリストのすべての要素を含むリストを作成できます。 。


私はそうあるべきShape s = ...だと信じていますT s = ...、さもなければreturn s;コンパイルすべきではありません。
polygenelubricants 2010

1

2つのSinglyLinkQueueをマージする以下のJamesGosling第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です。最初の方法は本にはありません。


1

私が理解している限り、ワイルドカードを使用すると、型パラメーターが不要な状況でより簡潔なコードを使用できます(たとえば、複数の場所で参照されているため、または他の回答で詳しく説明されているように複数の境界が必要なため)。

リンクで、私が(「ジェネリックメソッド」の下で)この方向を示唆する次のステートメントを読んだことを示します。

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

[...]

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

[...]

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


0

2番目の方法はもう少し冗長ですが、T内部を参照できます。

for (T shape : shapes) {
    ...
}

私が理解している限り、それが唯一の違いです。

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