最近、私はこの記事を読みました: 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);
}
}
どちらの方法を使用すればよいですか?この場合、ワイルドカードは役に立ちますか?
それはあなたが必要に依存します。このようなことをしたい場合は、境界型パラメーターを使用する必要があります。
_public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
if (shape.isPretty()) {
shapes.add(shape);
}
}
_
ここに_List<T> shapes
_と_T shape
_があるので、安全にshapes.add(shape)
できます。 _List<? extends Shape>
_と宣言された場合、[〜#〜] not [〜#〜]に安全にadd
することができます( _List<Square>
_およびCircle
)。
したがって、境界型パラメーターに名前を付けることで、ジェネリックメソッドの他の場所で使用するオプションがあります。もちろん、この情報は必ずしも必要なわけではないため、タイプ(たとえば、drawAll
)についてそれほど詳しく知る必要がない場合は、ワイルドカードだけで十分です。
境界型パラメーターを再度参照していない場合でも、複数の境界がある場合は境界型パラメーターが必要です。 Angelika Langer's Java Generics FAQs からの引用です
バインドされたワイルドカードと型パラメータのバインドの違いは何ですか?
ワイルドカードには1つの境界しか設定できませんが、型パラメーターには複数の境界を設定できます。ワイルドカードには下限または上限を設定できますが、型パラメーターの下限などはありません。
ワイルドカード境界と型パラメーター境界は、両方とも境界と呼ばれ、部分的に類似した構文を持っているため、しばしば混同されます。 […]
構文:
_type parameter bound T extends Class & Interface1 & … & InterfaceN wildcard bound upper bound ? extends SuperType lower bound ? super SubType
_ワイルドカードは、下限または上限のいずれか1つの境界のみを持つことができます。ワイルドカード境界のリストは許可されていません。
型パラメーターは、制約では、いくつかの境界を持つことができますが、型パラメーターの下限などはありません。
Effective Java 2nd Edition、Item 28:制限付きワイルドカードを使用してAPIの柔軟性を高める:
最大の柔軟性を得るには、プロデューサーまたはコンシューマーを表す入力パラメーターでワイルドカードタイプを使用します。 […] PECSは、Producer -
extends
、consumer -super
[…]の略です。戻り値の型としてワイルドカード型を使用しないでください。ユーザーに柔軟性を追加するのではなく、クライアントコードでワイルドカードタイプを使用するように強制します。適切に使用されたワイルドカードタイプは、クラスのユーザーにはほとんど見えません。メソッドは、メソッドに、受け入れるべきパラメーターを受け入れさせ、拒否すべきパラメーターを拒否させます。 クラスのユーザーがワイルドカードタイプについて考える必要がある場合、おそらくクラスのAPIに何か問題があります。
PECSの原則を適用して、次のようにaddIfPretty
の例に戻って、柔軟性を高めることができます。
_public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }
_
これで、addIfPretty
、たとえばCircle
を_List<Object>
_にできます。これは明らかにタイプセーフですが、元の宣言はそれを可能にするほど柔軟ではありませんでした。
<? super T>
_が何を意味し、いつそれを使用すべきか、この構造がどのように_<T>
_および_<? extends T>
_?と連携すべきかを説明できますか?この例では、Tを使用する必要はありません。他の場所ではそのタイプを使用しないためです。
しかし、次のようなことをした場合:
public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
T s = shapes.get(0);
s.draw(this);
return s;
}
または、リスト内の型パラメーターを別の型パラメーターと一致させたい場合は、polygenlubricantが言ったように:
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だけを返すよりももう少しタイプセーフティを取得します。たとえば、私のメソッドにList<Square>
を渡してから、結果のSquareをSquareのみを受け取るメソッドに渡すことができます。 「?」を使用した場合結果のShapeを正方形にキャストする必要がありますが、これはタイプセーフではありません。
2番目の例では、両方のリストの型パラメーターが同じであることを確認し(各「?」は異なるため、「?」では実行できません)、両方のリストのすべての要素を含むリストを作成できます。
私が理解している限り、ワイルドカードは、型パラメーターが不要な状況でより簡潔なコードを許可します(たとえば、複数の場所で参照されているため、または他の回答で詳しく説明されているように複数の境界が必要なため)。
リンクでは、この方向を示唆する次のステートメントを読んだことを示します(「ジェネリックメソッド」の下)。
ジェネリックメソッドを使用すると、型パラメーターを使用して、メソッドおよび/または戻り値型に対する1つ以上の引数の型間の依存関係を表現できます。そのような依存関係がない場合は、汎用メソッドを使用しないでください。
[...]
ワイルドカードの使用は、明示的な型パラメーターを宣言するよりも明確で簡潔であるため、可能な限り優先されるべきです。
[...]
ワイルドカードには、フィールドのタイプ、ローカル変数、配列として、メソッドシグネチャの外部で使用できるという利点もあります。
Java以下のJames Goslingによるプログラミング第4版の2つのSinglyLinkQueueをマージする場所からの例を検討してください。
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です。最初の方法は本にありません。
2番目の方法はもう少し冗長ですが、内部でT
を参照できます:
for (T shape : shapes) {
...
}
私の知る限り、それが唯一の違いです。