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のみに使うべきであり、外部に公開するものには使わない方が良いそうだ。詳細は説明を参照して欲しい。

しかし、非常に価値のあるパターンであり、回答者自身もフルーエントインターフェースを作るために仕事に使っていると認めている。これも、詳細は説明を参照して欲しい。