Javaジェネリクス:自身を返すメソッドを上位クラスで定義する
ジェネリクスを使うことによって、自分自身を返すメソッドを上位クラスで定義する方法がある。もちろん、下位クラスを追加する際に面倒な記述の必要はない。
問題の根源
ここでは、以下のようなクラス階層を使う。
Animal
+-- Dog
+-- Cat
クラス定義は以下のようなものだ(一つのJavaクラス内で使用できるようstaticにしてある)。
static class Animal {}
static class Dog extends Animal {}
static class Cat extends Animal {}
このとき、Animalにたくさんの共通メソッドを格納し、そのメソッドの返り値として、下位クラスを返すようにしたいのである。通常であればこんなところだろう。
static class Animal {
/** 動物の全長を指定する */
Animal setSize(int size) { ... return this; }
/** 動物の高さを指定する */
Animal setHeight(int height) { ... return this; }
}
static class Dog extends Animal {
Dog setSize(int size) { return (Dog)super.setSize(size); }
...
/** ほえ声を設定する */
Dog setBow(String bow) { ... return this; }
}
Dogオブジェクトのメソッドの返り値がDogオブジェクトであって欲しい理由としては、以下のようないわゆるフルーエントインターフェースにしたいからだ。
Dog aDog = new Dog().setSize(100).setBow("bowbow");
見てわかる通り、この方式ではAnimalのメソッドを追加していくと、その分だけDogにもCatにも追加しなければならなくなる。これはいかにも面倒だ。
解決策
以下のようにすればよい。
static class Animal<T extends Animal<T>> {
@SuppressWarnings("unchecked")
T setSize(int size) { return (T)this; }
@SuppressWarnings("unchecked")
T setHeight(int height) { return (T)this; }
}
static class Dog extends Animal<Dog> {
Dog setBow(String bow) { return this; }
}
この方式では、Animalに定義されたメソッドをDogで再定義する必要は無い。それでいてAnimalのメソッドの返り値がDog型になってくれる。
Dog aDog = new Dog().setSize(100).setBow("bowbow");
デメリット
しかしデメリットもある。Dog, Catの上位であるAnimalを扱いたい場合、そのままではWarningが出てしまうことだ。
これを避けるには例えば以下の記述としなければいけない。
List<Animal<?>>animalList = new ArrayList<>();
for (Animal<?>a: animalList) {
....
}
Stackoverflowでの議論
これについてもStackoverflowでの議論を発見した。
Is there a way to refer to the current type with a type variable?
おおよそは前述した通りのことだが、しかしここには回答者からの警告もある。
これはcuriously recurring template pattern(奇妙に再帰したパターン)と呼ばれ、「本質的に安全ではない」という。内部APIのみに使うべきであり、外部に公開するものには使わない方が良いそうだ。詳細は説明を参照して欲しい。
しかし、非常に価値のあるパターンであり、回答者自身もフルーエントインターフェースを作るために仕事に使っていると認めている。これも、詳細は説明を参照して欲しい。
ディスカッション
コメント一覧
まだ、コメントがありません