再帰型境界の概念を理解するために、簡単な問題を解いてみましょう。この概念は、実際の問題を解決することで理解しやすくなります。再帰型バインドの定義を最後に示します。概念を理解すると意味がわかるからです。
問題
果物をサイズで分類する必要があるとします。そして、同じ種類の果物しか比較できないと言われています。たとえば、リンゴとオレンジを比較することはできません (しゃれが意図されています)。
したがって、次のような単純な型階層を作成します。
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()
重複していることです。そして、将来的に新しい成果を生み出すために、から拡張するすべてのクラスでさらに複製されます。この例で繰り返されるコードの量はそれより少なくなりますが、実際には、繰り返されるコードは各クラスで数百行になる可能性があります。Apple
Orange
Fruit
共通クラスへの繰り返しコードの移動
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()
このステップでは、メソッドをスーパークラスに移動することで、メソッドの繰り返しコードを取り除きます。拡張されたクラスはApple
、Orange
共通のコードで汚染されなくなりました。
問題
ここでの問題は、さまざまな種類を比較できるようになったことです。リンゴとオレンジを比較してもエラーが発生しなくなりました。
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>
。Apple
とOrange
クラスに注意してください。これらは、それぞれタイプFruit<Apple>
andから継承するようになりFruit<Orange>
ました。ここで、異なるタイプを比較しようとすると、IDE はエラーを表示し、望ましい動作を示します。
apple1.compareTo(orange1); // Error: different types
問題
しかし、このステップでは、Fruit
クラスはコンパイルされません。のgetSize()
メソッドはT
、コンパイラには不明です。これはT
、
Fruit
クラスの型パラメーターにバインドがないためです。したがって、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()
のサブタイプにあることを認識します。Fruit
Apple
Orange
Comparable<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)
方法が得られます。これで、異なるタイプを比較しているときにエラーが発生しなくなり、突然リンゴとリンゴを比較できなくなりました。Orange
Apple
apple1.compareTo(apple2); // Error
apple1.compareTo(orange1); // No error
そのため、開発者はクラスを拡張する際に注意する必要があります。
それでおしまい!それが役立つことを願っています。