0
0
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Genericsでクラスインスタンスを外から注入するのはカッコ悪い

Last updated at Posted at 2024-07-12

Genericsは便利

JavaのGenericsは非常に便利ですね。大筋は似たような動作で詳細が少しだけ違う場合になどよく利用します。あとから詳細を実装したクラスを加えてやって求める動作を実現できます。
さて以下のような人間の心と行動をあとから注入するクラスを作ったとします。

空っぽの人間

空っぽの心

EmptyMind.java
package com.example.generics;

public abstract class EmptyMind {
	protected EmptyMind() {
		super();
	}

	public abstract String myMind();
}

空っぽの行動

EmptyAction.java
package com.example.generics;

public abstract class EmptyAction {
	protected EmptyAction() {
		super();
	}

	public abstract String myAction();
}

そして空っぽの人間

EmptyHuman.java
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!");
	}
}

ここに動く心と行動を派生させて人間らしくします。

恋人の心と行動

あなたの大切な方の心と行動を定義します。

LoversMind.java
package com.example.generics;

public class LoversMind extends EmptyMind {
	public LoversMind() {
		super();
	}

	@Override
	public String myMind() {
		return "love";
	}
}
LoversAction.java
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";
	}
}
そして出会い...
Encounter.java
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内部でメンバーの初期化を以下のように書き換えます。

EmptyHuman.java (改良版)
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は派生して使うようにしました。

恋人と敵を定義
Lover.java
package com.example.generics;

public class Lover extends EmptyHuman<LoversMind, LoversAction> {
	public Lover() {
		super();
	}
}

恋人には恋人の心と行動を

Enemy.java
package com.example.generics;

public class Enemy extends EmptyHuman<EnemysMind, EnemysAction> {
	public Enemy() {
		super();
	}
}

敵には敵の心と行動を

では再度遭遇しましょう
Encounter.java (改良版)
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クラスも定義したので引数を間違える心配も減りましたね。現実世界には本当は好きなのに「キライよ」という人もいますが、単純なプログラミングの世界なので。「愛している」と言いながら暴力をふるってくる人間もいますが、そんなヤツとはすぐに別れてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0