CRTPのパターンは、いわゆるエミュレートすることを可能にするセルフタイプでのJava、例えば:
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> implements Comparable<SELF> {
@Override
public final int compareTo(final SELF o) {
// ...
}
}
final class C1 extends AbstractFoo<C1> {
// ...
}
final class C2 extends AbstractFoo<C2> {
// ...
}
(上記のコードではComparable
、インターフェイスがちょうど明確にするために選択されている。もちろん、そこにいる他のユースケース)、私は簡単に2つのインスタンスを比較することができますC1
:
new C1().compareTo(new C1());
ではなくAbstractFoo
、その具体的な種類の異なる子孫:
new C1().compareTo(new C2()); // compilation error
しかし、パターンを乱用するのは簡単です:
final class C3 extends AbstractFoo<C1> {
// ...
}
// ...
new C3().compareTo(new C1()); // compiles cleanly
また、型チェックはすなわち、1つは簡単に混在させることができ、純粋にコンパイル時のものですC1
し、C2
インスタンス単一でTreeSet
、それらを相互に比較できます。
どのような代替CRTPにおけるJavaは、エミュレートし、自己の種類を上記のように乱用の可能性なし?
PSのみ-私はパターンが広く標準ライブラリで使用されていない観察EnumSet
とその子孫は、それを実装します。
私は、あなたが「虐待」で示したものを考えていません。使用するすべてのコードAbstractFoo
とはC3
限り、彼らがどんな危険なキャストをしないように、まだ完全にタイプセーフです。範囲SELF
内AbstractFoo
のコードが実際に頼ることができることを意味SELF
のサブタイプであるAbstractFoo<SELF>
が、そのコードが、実際に頼ることができないAbstractFoo<SELF>
のサブタイプですSELF
。したがって、たとえば、場合には、AbstractFoo
返されたメソッドを持っていたSELF
、そしてそれが返すことによって実装されましたthis
(それは本当に「自己型」であれば可能なはずのように)、それがコンパイルされないでしょう。
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF someMethod() {
return this; // would not compile
}
}
コンパイラは、それが安全ではないので、あなたがこれをコンパイルすることはできません。例えば、上でこのメソッドを実行するとC3
返されますthis
(実際にあるC3
タイプとインスタンス)C1
を呼び出すコードでクラスキャスト例外が発生します。あなたはキャストを使用して、コンパイラを過ぎてこそこそしようとした場合、のようなreturn (SELF)this;
、そして、あなたはそれが危険であることの責任を取ることを意味未チェックのキャスト警告が表示されます。
そして、あなたの場合は、AbstractFoo
本当に唯一という事実に頼っていたような方法で使用されてSELF extends AbstractFoo<SELF>
いるという事実に依存しない(バインドが言うように)、そしてAbstractFoo<SELF> extends SELF
、なぜあなたはの「虐待」を気にしませんか、C3
?あなたはまだあなたのクラス書くことができますC1 extends AbstractFoo<C1>
し、C2 extends AbstractFoo<C2>
罰金を。そして、他の誰かがクラスを記述することを決定した場合C3 extends AbstractFoo<C1>
、その後限り、彼らは方法でそれを書いている危険なキャストを使用せずに、コンパイラの保証、それはまだタイプセーフであることを、。おそらく、このようなAクラスには、便利な何かを行うことができない場合があります。知りません。しかし、それはまだ安全です。なぜそれが問題でしょうか?
以下のようにバインドされ、再帰的な理由<SELF extends AbstractFoo<SELF>>
で使用されていませんが、多くのものであるが、ほとんどの場合、それはどんなよりも有用ではありません<SELF>
。例えば、Comparable
インタフェースの型パラメータがバインドされていません。誰かがクラスを作成することを決定した場合Foo extends Comparable<Bar>
、彼らはそうすることができ、ほとんどのクラスやメソッドを使用することであるため、それは、非常に便利な、タイプセーフではないがComparable
、彼らは型変数持つ<T extends Comparable<? super T>>
ことが必要で、T
それ自身に匹敵するが、そうFoo
クラスは、それらの場所のいずれかに型引数として使用可能ではないであろう。しかし、それはまだ書き込みに誰かのために素晴らしいですFoo extends Comparable<Bar>
、彼らがしたい場合。
以下のようにバインドされ、再帰が唯一の場所で<SELF extends AbstractFoo<SELF>>
実際に実際の使用可能な場所にあるSELF
拡張AbstractFoo<SELF>
非常にまれであるが、。一つの場所を連鎖させることができ、オブジェクト自体を返すメソッドを持っていBuilderパターン、のようなものです。だからあなたのようなメソッドを持っている場合
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF foo() { }
public SELF bar() { }
public SELF baz() { }
}
あなたは一般的な値を持っていたAbstractFoo<?> x
あなたのようなもの行うことができますが、x.foo().bar().baz()
それは次のように宣言された場合、あなたが行うことができませんでしたがabstract class AbstractFoo<SELF>
。
現在の実装クラスと同じタイプでなければならないタイプのパラメータを作成するためのJavaジェネリックにおける方法はありません。、仮に、このようなメカニズムがあった場合は、その継承とトリッキーな問題を引き起こす可能性があります:
abstract class AbstractFoo<SELF must be own type> {
public abstract int compareTo(SELF o);
}
class C1 extends AbstractFoo<C1> {
@Override
public int compareTo(C1 o) {
// ...
}
}
class SubC1 extends C1 {
@Override
public int compareTo(/* should it take C1 or SubC1? */) {
// ...
}
}
ここでは、SubC1
暗黙的に継承しますAbstractFoo<C1>
が、休憩契約そのSELF
実装クラスの型でなければなりません。場合はSubC1.compareTo()
取らなければならないC1
引数を、受け取ったものの種類は、現在のオブジェクト自体と同じタイプであることはもはや真実ではありません。場合はSubC1.compareTo()
取ることができSubC1
、引数を、それはもはや上書きされないC1.compareTo()
スーパークラスのメソッドがかかるとして、それはもはや広いと引数のセットを取ると、。