Javaジェネリクス入門、その8



Javaジェネリクス入門、その7の続きである。JavaジェネリクスについてはJavaジェネリクスにまとめがあるので参照されたい。

今回は再帰的ジェネリクスというものを説明するのだが、その準備としてフルーエントインターフェース(fluent interface)というものを説明する。

フルーエントインターフェースとその有用性

※「インターフェース」とは言っても、Javaのinterfaceとは全く別物であるので注意。

例えば、以下のような動物クラスを作成する。

static class Animal {
  /** 動物の全長を指定する */
  void setSize(int size) { ... }
  /** 動物の高さを指定する */
  void setHeight(int height) { ... }
}

いろんな動物を作成し、それぞれが全長・高さが異なるものとする。複数の動物を作り、配列にまとめたいとすると、どうなるだろうか?

Animal a = new Animal();
a.setSize(500);
a.setHeight(100);
Animal b = new Animal();
b.setSize(600);
b.setHeight(150);
Animal[]array = { a, b };

などとするのだが、いちいち変数が必要になり、かなり面倒だ。

動物の定義を以下にように変更してみる。それぞれのメソッドが自身を返すようにする。

static class Animal {
  /** 動物の全長を指定する */
  Animal setSize(int size) { ...  return this; }
  /** 動物の高さを指定する */
  Animal setHeight(int height) { ... return this; }
}

すると以下のようにすっきりと書ける。

Animal[]array = {
  new Animal().setSize(500).setHeight(100),
  new Animal().setSize(600).setHeight(150)
};

もはや一時的な変数は不要になり、コンストラクタ呼び出しに続けて必要なメソッドを適切な引数で呼び出せばよい。

フルーエントインターフェースのクラスをサブクラス化する

しかし、上をもう少し一般化してみる。「動物」とはそもそも抽象的なものなので、直接のインスタンスは作らず、その下位のDogやCatのインスタンスを作ることにする。すると、こうなる。

static abstract class Animal { // abstractにした
  /** 動物の全長を指定する */
  Animal setSize(int size) { ...  return this; }
  /** 動物の高さを指定する */
  Animal setHeight(int height) { ... return this; }
}
static class Dog extends Animal {
}
static class Cat extends Animal {
}

以下のようになる。

Animal[]array = {
  new Dog().setSize(500).setHeight(100),
  new Cat().setSize(600).setHeight(150)
};

しかし、ここで重要なこととしては、一度でもメソッドを呼び出すと、その返り値はAnimalになっていることだ。したがって、DogやCatに独自のメソッドを追加した場合、そのままではそれを呼び出すことができない。

例えば、犬に吠え声設定メソッドを追加する。

static clas Dog extends Animal {
  /** 吠え声を設定する */
  Dog setBow(String bow) { ... return this; }
}

以下は可能だが、

  new Dog().setBow("bowbow").setSize(500).setHeight(100),

以下はできない

  new Dog().setSize(500).setHeight(100).setBow("bowbow"),

なぜなら、setHeight()の戻り値はAnimalなので、setBow()というメソッドが無いからだ。

サブクラス側で返り値の型を変更する

上のような場合、サブクラス側で元のメソッドをオーバライドし、返り値の型を変更することができる。

static class Dog extends Animal {
  @Override
  Dog setSize(int size) { super.setSize(size); return this; }
  @Override
  Dog setHeight(int height) { super.setHeight(height); return this; }

  /** 吠え声を設定する */
  Dog setBow(String bow) { ... return this; }
}

このようにすれば、すべてのメソッドで返り値をDogにできる。が、いかにもこれは面倒だ。

再帰的ジェネリクス

そこで元のAnimalクラスの定義を変更する。これを再帰的ジェネリクス(recursive generics)と呼ぶらしい。

  static abstract 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; }
  }

その上で、DogやCatを以下のように定義する。

  static class Dog extends Animal<Dog> {    
    Dog setBow(String bow) { return this; }
  }

  public static class Cat extends Animal<Cat> {    
    Cat setMew(String mew) { return this; }
  }

こうすれば、DogインスタンスのすべてのメソッドがDogを返すことになり、新たに追加したメソッドはどこでも呼び出せることになる。

  new Dog().setSize(500).setBow("bowbow").setHeight(100),