Joshua Bloch による素晴らしい「Effective Java」を読みました。しかし、本の中の 1 つの例は、私には不明確なままです。ジェネリックに関する章から引用したもので、正確な項目は「項目 28: 境界付きワイルドカードを使用して API の柔軟性を高める」です。
この項目では、制限付きの型パラメーターと制限付きのワイルドカード型を使用して、コレクションから最大要素を選択するアルゴリズムの最も普遍的で防弾 (型システムの観点から) バージョンを作成する方法を示します。
記述された静的メソッドの最終的な署名は次のようになります。
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
Collections#max
そして、それは標準ライブラリの関数のものとほとんど同じです。
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
型制約で制限付きワイルドカードが必要な理由は理解できT extends Comparable<? super T>
ますが、引数の型には本当に必要なのでしょうか? List<T>
またはだけ残しても同じように思えCollection<T>
ますね。私は次のようなことを意味します:
public static <T extends Comparable<? super T>> T wrongMin(Collection<T> xs)
両方の署名を使用する次のばかげた例を書きましたが、違いはありません。
public class Algorithms {
public static class ColoredPoint extends Point {
public final Color color;
public ColoredPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
@Override
public String toString() {
return String.format("ColoredPoint(x=%d, y=%d, color=%s)", x, y, color);
}
}
public static class Point implements Comparable<Point> {
public final int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return String.format("Point(x=%d, y=%d)", x, y);
}
@Override
public int compareTo(Point p) {
return x != p.x ? x - p.x : y - p.y;
}
}
public static <T extends Comparable<? super T>> T min(Collection<? extends T> xs) {
Iterator<? extends T> iter = xs.iterator();
if (!iter.hasNext()) {
throw new IllegalArgumentException("Collection is empty");
}
T minElem = iter.next();
while (iter.hasNext()) {
T elem = iter.next();
if (elem.compareTo(minElem) < 0) {
minElem = elem;
}
}
return minElem;
}
public static <T extends Comparable<? super T>> T wrongMin(Collection<T> xs) {
return min(xs);
}
public static void main(String[] args) {
List<ColoredPoint> points = Arrays.asList(
new ColoredPoint(1, 2, Color.BLACK),
new ColoredPoint(0, 2, Color.BLUE),
new ColoredPoint(0, -1, Color.RED)
);
Point p1 = wrongMin(points);
Point p2 = min(points);
System.out.println("Minimum element is " + p1);
}
では、そのような単純化された署名が受け入れられない例を提案できますか?
PSそして、なぜT extends Object
公式の実装にあるのですか?
答え
まあ、@Bohemian のおかげで、私はそれらの違いを理解することができました。
次の 2 つの補助的な方法を検討してください。
private static void expectsPointOrColoredPoint(Point p) {
System.out.println("Overloaded for Point");
}
private static void expectsPointOrColoredPoint(ColoredPoint p) {
System.out.println("Overloaded for ColoredPoint");
}
確かに、スーパークラスとそのサブクラスの両方でメソッドをオーバーロードするのはあまりスマートではありませんが、実際に推論された戻り値の型を確認できます (前points
とList<ColoredPoint>
同じです)。
expectsPointOrColoredPoint(min(points)); // print "Overloaded for ColoredPoint"
expectsPointOrColoredPoint(wrongMin(points)); // print "Overloaded for ColoredPoint"
どちらの方法でも、推論された型は でしたColoredPoint
。
オーバーロードされた関数に渡される型について明示したい場合があります。いくつかの方法があります。
あなたはキャストすることができます:
expectsPointOrColoredPoint((Point) min(points)); // print "Overloaded for Point"
expectsPointOrColoredPoint((Point) wrongMin(points)); // print "Overloaded for Point"
それでも変わらない…
または、次の構文を使用して、どの型を推論する必要があるかをコンパイラに伝えることができますclass.<type>method
。
expectsPointOrColoredPoint(Algorithms.<Point>min(points)); // print "Overloaded for Point"
expectsPointOrColoredPoint(Algorithms.<Point>wrongMin(points)); // will not compile
あはは!これが答えです。ジェネリックは (配列とは異なり) 共変ではないため、List<ColoredPoint>
期待する関数に渡すことはできませんが、期待する関数に渡すことはできます。Collection<Point>
Collection<? extends Point>
そのような場合、どこで、または誰が明示的な型パラメーターを使用することを好むかはわかりませんが、少なくとも、wrongMin
不適切である可能性がある場所を示しています。
T extends Object
そして、制約の目的について回答してくれた @erickson と @tom-hawtin-tackline に感謝します。