Genericsと型引数の応用(Java)
型引数の応用
ここでは、Genericsで導入された「型引数」について、いくつかもサンプルを示したいと思います。型引数を受け取るクラスのサンプル
「型引数」を受け取るクラスを作って見ましょう。同時にメソッドにも型を引数で渡すようにします。<Action.java>
package beans;
public abstract class Action {
public Action() {
super();
}
abstract public void doIt();
}
<Eat.java>
package beans;
public class Eat extends Action{
String food;
public Eat(String _food) {
this.food=_food;
}
public void doIt() {
System.out.println(food + "を食べました。");
}
}
これら2つのクラスは継承関係にあります。
次にこのActionクラスを仕様するクラスを作成します。
<SomethigToDo.java>
package beans;
public class SomethigToDo<E extends Action>{ // (a)
public SomethigToDo() {
super();
}
public void justDoIt(E obj){
obj.doIt(); // (b)
}
}
このクラスはEを型引数として受け取ります。抽象したActionクラスを使うことを宣言するのが、(a)行の<E extends Action>の部分で、これにより、ActionクラスのメソッドであるdoIt()メソッドが呼べるようになります(Actionクラスの利用を宣言しないと、(b)行の
obj.doIt();
がコンパイルエラーとなります)。
次にこのSomethigToDoクラスを使用するクラスを作成します。
<ExampleGenerics2.java>
package test;
import beans.Eat;
import beans.SomethigToDo;
public class ExampleGenerics2 {
public static void main(String[] args) {
SomethigToDo<Eat> _act = new SomethigToDo<Eat>();
String _str = "バナナ";
_act.justDoIt(new Eat(_str));
}
}
実行結果は以下のようになります。
バナナを食べました。
型引数を受け取るクラスのサンプル2
上で示したサンプルでは、SomethingToDoクラスが抽象クラスActionを利用することで、具象クラスEatとSomethingToDo間である程度の祖結合を実現していました。以下ではこれをもう少し推し進めてみましょう。
<ActionInterface.java>
package beans;
public interface ActionInterface {
abstract public void doIt();
}
次にAction.javaを以下のように直します。
<Action.java>
package beans;
public abstract class Action implements ActionInterface {}
他のプログラムに変更はありません。これで、定石通りメソッドをインターフェイスで記述することができました。でも、かなり冗長な感じがしますね。これは、SomethingToDo.javaの(a)がextendsをサポートしている反面、implementsをサポートしていないためです(このため、一度、Action.javaという空のクラスを経由しています)。
型引数を受け取るメソッドのサンプル
次は「メソッドが型引数を受け取る」サンプルを作ってみます。以下の2つのクラスを作ってみましょう。
<SomethigToDo1.java>
package beans;
import java.util.List;
public class SomethigToDo1{
public SomethigToDo1() {
super();
}
public void justDoIt(List<? extends Action> obj){
for(Action el : obj){ // (A)
el.doIt();
}
}
}
<ExampleGenerics2.java>
package test;
import java.util.ArrayList;
import java.util.List;
import beans.Eat;
import beans.SomethigToDo1;
public class ExampleGenerics3 {
public static void main(String[] args) {
List<Eat> _list = new ArrayList<Eat>();
_list.add(new Eat("バナナ"));
_list.add(new Eat("イチゴ"));
_list.add(new Eat("パイナップル"));
SomethigToDo1 _act = new SomethigToDo1();
_act.justDoIt(_list);
}
}
他のプログラムは変えません。実行結果は以下のようになります。
バナナを食べました。 イチゴを食べました。 パイナップルを食べました。
もう少し突っ込んでみましょう。以下の2つを作ってみましょう。
<Drink.java>
package beans;
public class Drink extends Action{
String drink;
public Drink(String _drink) {
this.drink=_drink;
}
public void doIt() {
System.out.println(drink + "を飲みました。");
}
}
<ExampleGenerics4>
package test;
import java.util.ArrayList;
import java.util.List;
import beans.Action;
import beans.Drink;
import beans.Eat;
import beans.SomethigToDo1;
public class ExampleGenerics4 {
public static void main(String[] args) {
List<Action> _list = new ArrayList<Action>();
_list.add(new Eat("バナナ"));
_list.add(new Drink("イチゴジュース"));
_list.add(new Eat("パイナップル"));
_list.add(new Drink("ブドウジュース"));
SomethigToDo1 _act = new SomethigToDo1();
_act.justDoIt(_list);
}
}
このサンプルは簡単なストラテジーパターンになっています。
先ほども述べましたが、Genericsはインターフェイス・プログラミングに固執すると冗長性を持つように思いますが、抽象化を促進することでクラス間の祖結合化をサポートするものとも考えることができると思います。
(インターフェイスではなく)「抽象クラスによる祖結合」という感触です。
2007年02月27日(火) 19:57:49 Modified by wanderingse