17

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");
}

確かに、スーパークラスとそのサブクラスの両方でメソッドをオーバーロードするのはあまりスマートではありませんが、実際に推論された戻り値の型を確認できます (前pointsList<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 に感謝します。

4

3 に答える 3

5

違いは、返される型にあり、特にinferenceの影響を受けます。これにより、型は階層的に Comparable 型と List 型の間の型になる場合があります。例を挙げましょう:

class Top {
}
class Middle extends Top implements Comparable<Top> {
    @Override
    public int compareTo(Top o) {
        // 
    }
}
class Bottom extends Middle {
}

あなたが提供した署名を使用して:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)

エラー、警告、または (重要な) キャストなしでこれをコーディングできます。

List<Bottom> list;
Middle max = max(list); // T inferred to be Middle

結果が必要な場合はMiddle、推論なしで、次の呼び出しを明示的に入力できますMiddle

 Comparable<Top> max = MyClass.<Middle>max(list); // No cast

または受け入れるメソッドに渡すMiddle(推論が機能しない場合)

someGenericMethodThatExpectsGenericBoundedToMiddle(MyClass.<Middle>max(list));

これが役立つかどうかはわかりませんが、コンパイラが許可/推論する型を説明するために、署名は次のようになります (もちろん、これがコンパイルされるわけではありません)。

public static <Middle extends Comparable<Top>> Middle max(List<Bottom> list)
于 2013-06-22T21:22:56.273 に答える
2

の違い

T max(Collection<? extends T> coll)

T wrongMax(Collection<T> xs)

2 番目のバージョンの戻り値の型はコレクションの要素の型とまったく同じですがT、最初のバージョンTでは要素の型のスーパー型になる可能性があります。

2 番目の質問: の理由は、インターフェイスではなくクラスであるT extends Objectことを確認します。T


更新:違いのもう少し「自然な」デモンストレーション: 次の 2 つのメソッドを定義するとします。

static void happy(ColoredPoint p, Point q) {}
static void happy(Point p, ColoredPoint q) {}

そして、最初のものを次のように呼び出します。

happy(coloredPoint, min(points));
happy(coloredPoint, wrongMin(points));

min型推論エンジンは、最初の呼び出しで の戻り値の型が である必要がPointあり、コードがコンパイルされると推測できます。happyへの呼び出しがあいまいであるため、2 番目の呼び出しはコンパイルに失敗します。

残念ながら、型推論エンジンは少なくとも Java 7 では十分に強力ではないため、実際には両方の呼び出しがコンパイルに失敗します。違いは、最初の呼び出しは のように型パラメーターを指定することで修正できるのAlgorithms.<Point>minに対し、2 番目の呼び出しを修正するには明示的なキャストが必要になることです。

于 2013-06-22T21:05:59.337 に答える
1

簡単なことではありませんが、できるだけ具体的にしようと思います。

ではT max(Collection<? extends T> coll) this のような引数を渡すことができList<Animal> or List<Cat> or List<Dog>T wrongMax(Collection<T> xs) T が Animal の場合、引数として渡すことはできません。これ List<Dog>, List<Cat>はもちろん、実行時に Cat または Dog オブジェクトを追加できますList<Animal>が、コンパイル時にサブクラスを渡すことはできません。メソッドの引数として渡されるリストのタイプの動物wrongMaxの一方、maxメソッドでは可能です。私の英語で申し訳ありません、私はまだそれを学んでいます:)、よろしく。

于 2013-06-22T21:32:07.733 に答える