28

私は、Effective Java [項目 27] の Generics に関する章を読んでいます。

本の中にこんな一節があります。

比較的まれではありますが、型パラメーターがその型パラメーター自体を含む何らかの式によって境界付けられることは許容されます。これは、再帰型バインドとして知られているものです。

この:

// Using a recursive type bound to express mutual comparability
public static <T extends Comparable<T>> T max(List<T> list) {...}

再帰型バインドとは何ですか? また、上記のコードは相互比較可能性を達成するのにどのように役立ちますか?

4

3 に答える 3

31

再帰型バインドとは

これ:<T extends Comparable<T>>

型パラメータTもスーパー インターフェイスの署名の一部であることに注意してくださいComparable<T>

また、上記のコードは相互の比較可能性を達成するのにどのように役立ちますか?

タイプ のオブジェクトのみを比較できることが保証されますT。タイプ バウンドなしで、Comparable任意の 2 つの を比較しますObject。型がバインドされているため、コンパイラは型の 2 つのオブジェクトのみTが比較されることを保証できます。

于 2011-09-12T09:53:03.093 に答える
15

再帰型境界の概念を理解するために、簡単な問題を解いてみましょう。この概念は、実際の問題を解決することで理解しやすくなります。再帰型バインドの定義を最後に示します。概念を理解すると意味がわかるからです。


問題

果物をサイズで分類する必要があるとします。そして、同じ種類の果物しか比較できないと言われています。たとえば、リンゴとオレンジを比較することはできません (しゃれが意図されています)。

したがって、次のような単純な型階層を作成します。

Fruit.java

interface Fruit {
    Integer getSize();
}

Apple.java

class Apple implements Fruit, Comparable<Apple> {
    private final Integer size;

    public Apple(Integer size) {
        this.size = size;
    }

    @Override public Integer getSize() {
        return size;
    }

    @Override public int compareTo(Apple other) {
        return size.compareTo(other.size);
    }
}

オレンジ.java

class Orange implements Fruit, Comparable<Orange> {
    private final Integer size;

    public Orange(Integer size) {
        this.size = size;
    }

    @Override public Integer getSize() {
        return size;
    }

    @Override public int compareTo(Orange other) {
        return size.compareTo(other.size);
    }
}

Main.java

class Main {
    public static void main(String[] args) {
        Apple apple1 = new Apple(3);
        Apple apple2 = new Apple(4);
        apple1.compareTo(apple2);

        Orange orange1 = new Orange(3);
        Orange orange2 = new Orange(4);
        orange1.compareTo(orange2);

        apple1.compareTo(orange1);  // Error: different types
    }
}

解決

このコードでは、同じタイプ、つまりリンゴとリンゴ、オレンジとオレンジを比較できるようにするという目的を達成できます。リンゴとオレンジを比較すると、必要なエラーが発生します。

問題

ここでの問題は、メソッドを実装するためのコードがクラスとcompareTo()重複していることです。そして、将来的に新しい成果を生み出すために、から拡張するすべてのクラスでさらに複製されます。この例で繰り返されるコードの量はそれより少なくなりますが、実際には、繰り返されるコードは各クラスで数百行になる可能性があります。AppleOrangeFruit


共通クラスへの繰り返しコードの移動

Fruit.java

class Fruit implements Comparable<Fruit> {
    private final Integer size;

    public Fruit(Integer size) {
        this.size = size;
    }

    public Integer getSize() {
        return size;
    }

    @Override public int compareTo(Fruit other) {
        return size.compareTo(other.getSize());
    }
}

Apple.java

class Apple extends Fruit {
    public Apple(Integer size) {
        super(size);
    }
}

オレンジ.java

class Orange extends Fruit {
    public Orange(Integer size) {
        super(size);
    }
}

解決

compareTo()このステップでは、メソッドをスーパークラスに移動することで、メソッドの繰り返しコードを取り除きます。拡張されたクラスはAppleOrange共通のコードで汚染されなくなりました。

問題

ここでの問題は、さまざまな種類を比較できるようになったことです。リンゴとオレンジを比較してもエラーが発生しなくなりました。

apple1.compareTo(orange1);    // No error

型パラメータの導入

Fruit.java

class Fruit<T> implements Comparable<T> {
    private final Integer size;

    public Fruit(Integer size) {
        this.size = size;
    }

    public Integer getSize() {
        return size;
    }

    @Override public int compareTo(T other) {
        return size.compareTo(other.getSize());     // Error: getSize() not available.
    }
}

Apple.java

class Apple extends Fruit<Apple> {
    public Apple(Integer size) {
        super(size);
    }
}

オレンジ.java

class Orange extends Fruit<Orange> {
    public Orange(Integer size) {
        super(size);
    }
}

解決

異なる型の比較を制限するために、型パラメーターを導入しますT。したがって、比較Fruit<Apple>可能なものを比較可能なものと比較することはできませんFruit<Orange>AppleOrangeクラスに注意してください。これらは、それぞれタイプFruit<Apple>andから継承するようになりFruit<Orange>ました。ここで、異なるタイプを比較しようとすると、IDE はエラーを表示し、望ましい動作を示します。

apple1.compareTo(orange1);  // Error: different types

問題

しかし、このステップでは、Fruitクラスはコンパイルされません。のgetSize()メソッドはT、コンパイラには不明です。これはTFruitクラスの型パラメーターにバインドがないためです。したがって、T任意のクラスである可能性があります。すべてのクラスがgetSize()メソッドを持つことはできません。したがって、コンパイラが のgetSize()メソッドを認識しないのは正しいことですT


再帰型バウンドの導入

Fruit.java

class Fruit<T extends Fruit<T>> implements Comparable<T> {
    private final Integer size;

    public Fruit(Integer size) {
        this.size = size;
    }

    public Integer getSize() {
        return size;
    }

    @Override public int compareTo(T other) {
        return size.compareTo(other.getSize());     // Now getSize() is available.
    }
}

Apple.java

class Apple extends Fruit<Apple> {
    public Apple(Integer size) {
        super(size);
    }
}

オレンジ.java

class Orange extends Fruit<Orange> {
    public Orange(Integer size) {
        super(size);
    }
}

最終的解決

したがって、コンパイラに、自分Tのサブタイプが のサブタイプであることを伝えますFruit。つまり、上限を指定しますT extends Fruit<T>。これにより、 のサブタイプのみFruitが型引数として許可されるようになります。コンパイラは、メソッドを含むtype( ) も受け取るため、メソッドがクラス (など)getSize()のサブタイプにあることを認識します。FruitAppleOrangeComparable<T>Fruit<T>getSize()

これにより、メソッドの繰り返しコードを取り除くことがcompareTo()でき、同じ種類の果物、リンゴとリンゴ、オレンジとオレンジを比較することもできます。

これで、質問で指定された関数compareTo()内でメソッドを使用できます。max()


再帰型境界の定義

ジェネリックスでは、参照型自体によってバインドされた型パラメーターが参照型にある場合、その型パラメーターは再帰型バインドを持つと言われます。

この例では、ジェネリック型Fruit<T extends Fruit<T>>Fruit参照型であり、その型パラメーターTはそれ自体によって境界付けられているFruitため、型パラメーターTには再帰型 bound がありFruit<T>ます。

再帰型とは、その型自体を何らかの引数またはその戻り値の型として使用する関数を含む型です。この例でcompareTo(T other)は、引数として同じ再帰型を取る再帰型の関数です。


警告

このパターンには注意点があります。コンパイラは、別のサブタイプの型引数を持つクラスを作成することを妨げません:

class Orange extends Fruit<Orange> {...}
class Apple extends Fruit<Orange> {...}    // No error

Apple上記のクラスでは、誤ってそれ自体Orangeの代わりにApple型引数として渡したことに注意してください。これにより、 の代わりに取得するcompareTo(T other)方法が得られます。これで、異なるタイプを比較しているときにエラーが発生しなくなり、突然リンゴとリンゴを比較できなくなりました。OrangeApple

apple1.compareTo(apple2);     // Error
apple1.compareTo(orange1);    // No error

そのため、開発者はクラスを拡張する際に注意する必要があります。


それでおしまい!それが役立つことを願っています。

于 2020-11-18T21:31:19.343 に答える
8

そのような宣言の詳細を説明する Angelika Langer によって書かれた Java Generics FAQ にエントリがあります。

于 2011-09-12T10:11:12.550 に答える