可変長な型変数の表現

さて、前回のエントリを枕として、JavaでTuple的なモノのサンプルを書いたのだけど、汎用のTupleというか、値を2つだけとるPairとでも名乗るべきモノだった。

みなさん御存知の通り、型変数は可変長に宣言することができない。そもそもJavaの文法的には型変数が宣言する方法は2種類だけだった。

  • ひとつはクラス宣言に伴うインスタンススコープの型変数
  • ひとつはメソッド宣言に伴うメソッドスコープの型変数

なので、常識的に考えて、可変長な型変数を取るような機構を作ることはできないことになる。

エンクロージャ

そこで非常識に考えるわけだが、昔、Javaによる高階型変数の実装 - プログラマーの脳みその時にも使ったアレ、内部クラスの出番です。

内部クラスについてよく知らない人はJavaのクラス宣言5種+α - プログラマーの脳みそあたりを参考にしてください。

イメージとしてはクラス-インスタンス関係のように、インスタンス-子インスタンスといった関係を作ることができる。このとき、親のインスタンスで宣言された型変数は子のインスタンスでも使うことができる。

そこで、前回のTupleに内部クラスを導入します。内部クラスはいりまーす。

public class Tuple<T1, T2> {
	public T1 t1;
	public T2 t2;
	public Tuple(T1 t1, T2 t2) {
		this.t1 = t1;
		this.t2 = t2;
	}
	/** タプルの拡張 */
	public class Ex<T3> {
		public T3 t3;
		public Ex(T3 t3) {
			this.t3 = t3;
		}
	}
}

いやまあ、これをやったところで、Tupleのインスタンスを作った後にひとつ値を足すことができるだけなんですけどね。

Tuple<String, Integer> t1 = new Tuple<>("hoge", 123);
Tuple<String, Integer>.Ex<Boolean> ex = t1.new Ex<>(true);

このExがまたTupleだったらなー。そうだ、Tupleを継承させよう!

public class Tuple<T1, T2> {
	public T1 t1;
	public T2 t2;
	public Tuple(T1 t1, T2 t2) {
		this.t1 = t1;
		this.t2 = t2;
	}
	@Override
	public String toString() {
		return ""+t1+", "+t2;
	}

	/** タプルの拡張 */
	public class Ex<T3> extends Tuple<Tuple<T1, T2>, T3> {
		public Ex(T3 t3) {
			super (Tuple.this, t3);
		}
	}
}

使い方はどうなるか

Tuple<String, Integer> t1 =
	new Tuple<>("hoge", 123);
System.out.println(t1);

Tuple<String, Integer>.Ex<Boolean> t2 =
	t1.new Ex<>(true);
System.out.println(t2);

Tuple<Tuple<String, Integer>,Boolean>.Ex<String> t3 =
	t2.new Ex<>("piyo");
System.out.println(t3);

実行結果が以下のとおり

hoge, 123
hoge, 123, true
hoge, 123, true, piyo

まぁ、なかなかイイんじゃないでしょうかね。

TupleIteratorの実装

Tupleの実装方式と同じやりかたを使ってTupleIteratorも実装してみよう。

public class TupleIterator<T1, T2>
implements Iterator<Tuple<T1, T2>> {
	Iterator<? extends T1> ite1;
	Iterator<? extends T2> ite2;

	/**
	 * 2つのItaratorからTupleIteratorを作る
	 */
	public TupleIterator(Iterator<? extends T1> ite1, Iterator<? extends T2> ite2) {
		this.ite1 = ite1;
		this.ite2 = ite2;
	}

	/**
	 * @throws IllegalStateException ふたつのItaratorの要素数が不一致の場合
	 */
	@Override
	public boolean hasNext() {
		boolean n1 = ite1.hasNext();
		boolean n2 = ite2.hasNext();
		if (n1 ^ n2) {
			throw new IllegalStateException("個数の不一致");
		}
		return n1;
	}

	@Override
	public Tuple<T1, T2> next() {
		return new Tuple<T1, T2>(ite1.next(), ite2.next());
	}

	@Override
	public void remove() {
		ite1.remove();
		ite2.remove();
	}

	/** 拡張 */
	public class Ex<T3> extends TupleIterator<Tuple<T1, T2>, T3> {
		public Ex(Iterator<? extends T3> ite3) {
			super(TupleIterator.this, ite3);
		}
	}

}

実行するには以下のように扱う

List<String> sList = new ArrayList<>();
sList.add("いち");
sList.add("に");
List<Integer> iList = new ArrayList<>();
iList.add(123);
iList.add(234);
List<Boolean> bList = new ArrayList<>();
bList.add(true);
bList.add(false);

TupleIterator<String, Integer> ti =
		new TupleIterator<>(sList.iterator(), iList.iterator());
TupleIterator<String, Integer>.Ex<Boolean> ex =
		ti.new Ex<>(bList.iterator());

while (ex.hasNext()) {
	Tuple<Tuple<String,Integer>,Boolean> next = ex.next();
	System.out.println(next);
}

実行結果は以下のとおり

いち, 123, true
に, 234, false

まとめ

  • 型変数を可変長にしたかったので内部クラスを使った
  • 内部クラスを extends 外部クラスとして自己相似形とする
  • 型変数をextends Tuple, T3>といった形で入れ子にすることで型を表現
  • 使う際は .new Ex<>(value) を繋げることで値を増やしていける

こんなんで良いのだろうか