Genericsは便利
JavaのGenericsは非常に便利ですね。大筋は似たような動作で詳細が少しだけ違う場合になどよく利用します。あとから詳細を実装したクラスを加えてやって求める動作を実現できます。
さて以下のような人間の心と行動をあとから注入するクラスを作ったとします。
空っぽの人間
空っぽの心
package com.example.generics;
public abstract class EmptyMind {
protected EmptyMind() {
super();
}
public abstract String myMind();
}
空っぽの行動
package com.example.generics;
public abstract class EmptyAction {
protected EmptyAction() {
super();
}
public abstract String myAction();
}
そして空っぽの人間
package com.example.generics;
public class EmptyHuman <T1 extends EmptyMind, T2 extends EmptyAction> {
private T1 mind = null;
private T2 action = null;
public EmptyHuman(T1 mind, T2 action) {
super();
this.mind = mind;
this.action = action;
}
public void showMyMind() {
System.out.println("I " + mind.myMind() + " you!");
}
public void takeMyAction() {
System.out.println("I'm gonna " + action.myAction() + " you!");
}
}
ここに動く心と行動を派生させて人間らしくします。
恋人の心と行動
あなたの大切な方の心と行動を定義します。
package com.example.generics;
public class LoversMind extends EmptyMind {
public LoversMind() {
super();
}
@Override
public String myMind() {
return "love";
}
}
package com.example.generics;
public class LoversAction extends EmptyAction {
public LoversAction() {
super();
}
@Override
public String myAction() {
return "kiss";
}
}
敵も存在するのが現実というもの...
世の中にはいい人ばかり存在するわけではありませんね。
package com.example.generics;
public class EnemysMind extends EmptyMind {
public EnemysMind() {
super();
}
@Override
public String myMind() {
return "hate";
}
}
package com.example.generics;
public class EnemysAction extends EmptyAction {
public EnemysAction() {
super();
}
@Override
public String myAction() {
return "kick";
}
}
そして出会い...
package com.example.generics.realworld;
import com.example.generics.EmptyHuman;
import com.example.generics.EnemysAction;
import com.example.generics.EnemysMind;
import com.example.generics.LoversAction;
import com.example.generics.LoversMind;
public class Encounter {
public static void main(String[] args) {
System.out.println("Encounter an lover.");
EmptyHuman<?, ?> lover = new EmptyHuman<>(new LoversMind(), new LoversAction());
lover.showMyMind();
lover.takeMyAction();
System.out.println("Encounter the enemy.");
EmptyHuman<?, ?> enemy = new EmptyHuman<>(new EnemysMind(), new EnemysAction());
enemy.showMyMind();
enemy.takeMyAction();
}
}
コンパイルと実行
$ javac com/example/generics/*.java
$ javac -cp . com/example/generics/realworld/*.java
$ java -cp . com.example.generics.realworld.Encounter
Encounter an lover.
I love you!
I'm gonna kiss you!
Encounter the enemy.
I hate you!
I'm gonna kick you!
結果は望み通りですが、コードの一部が気に入りません。
EmptyHuman<?, ?> lover = new EmptyHuman<>(new LoversMind(), new LoversAction());
EmptyHuman<?, ?> enemy = new EmptyHuman<>(new EnemysMind(), new EnemysAction());
この2行です。
EmptyHumanコンストラクタ引数2つがともにコンストラクタになっています。この引数をなくしたいです。
ParameterizedTypeというものがあるらしい
総称型と訳すらしいのですが、詳しい説明は他をご覧いただきたいのですが、Genericsのパラメータ型をうまいことしてくれる仕組みの使い方を少しばかり。
EmptyHumanクラスには型情報が渡されるのでEmptyHuman内部でメンバーの初期化を以下のように書き換えます。
package com.example.generics;
import java.lang.reflect.ParameterizedType;
public class EmptyHuman <T1 extends EmptyMind, T2 extends EmptyAction> {
private T1 mind = null;
private T2 action = null;
protected EmptyHuman() {
super();
if (!humanization()) {
System.out.println("I want to become human as soon as possible...");
System.exit(-1); // 早く人間になりたい...
}
}
public boolean humanization() {
try {
// ここで心と行動をここで作成
mind = getMindClass().newInstance();
action = getActionClass().newInstance();
return true;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace(System.err);
return false;
}
}
@SuppressWarnings("unchecked")
private Class<T1> getMindClass() {
ParameterizedType paramType = (ParameterizedType) getClass().getGenericSuperclass();
// 第一引数のクラスを取得
return (Class<T1>) paramType.getActualTypeArguments()[0];
}
@SuppressWarnings("unchecked")
private Class<T2> getActionClass() {
ParameterizedType paramType = (ParameterizedType) getClass().getGenericSuperclass();
// 第二引数のクラスを取得
return (Class<T2>) paramType.getActualTypeArguments()[1];
}
public void showMyMind() {
System.out.println("I " + mind.myMind() + " you!");
}
public void takeMyAction() {
System.out.println("I'm gonna " + action.myAction() + " you!");
}
}
EmptyHumanは派生して使うようにしました。
恋人と敵を定義
package com.example.generics;
public class Lover extends EmptyHuman<LoversMind, LoversAction> {
public Lover() {
super();
}
}
恋人には恋人の心と行動を
package com.example.generics;
public class Enemy extends EmptyHuman<EnemysMind, EnemysAction> {
public Enemy() {
super();
}
}
敵には敵の心と行動を
では再度遭遇しましょう
package com.example.generics.realworld;
import com.example.generics.Enemy;
import com.example.generics.Lover;
public class Encounter {
public static void main(String[] args) {
System.out.println("Encounter an lover.");
Lover lover = new Lover();
lover.showMyMind();
lover.takeMyAction();
System.out.println("Encounter the enemy.");
Enemy enemy = new Enemy();
enemy.showMyMind();
enemy.takeMyAction();
}
}
以下の2行がかなりスッキリしました。
Lover lover = new Lover();
Enemy enemy = new Enemy();
再度コンパイルと実行
$ javac com/example/generics/*.java
$ javac -cp . com/example/generics/realworld/*.java
$ java -cp . com.example.generics.realworld.Encounter
Encounter an lover.
I love you!
I'm gonna kiss you!
Encounter the enemy.
I hate you!
I'm gonna kick you!
結果は以前となんら変わりませんが、カッコ悪いコードを少しだけカッコよくできたと思います。
LoverクラスとEnemyクラスも定義したので引数を間違える心配も減りましたね。現実世界には本当は好きなのに「キライよ」という人もいますが、単純なプログラミングの世界なので。「愛している」と言いながら暴力をふるってくる人間もいますが、そんなヤツとはすぐに別れてください。