JavaでC#のas演算子の様なものを実装してみた

どう考えてもなかなか抽象化が難しく、instanceofなどの動的型変換を使った方が有効な場合もある。
例えば、「様々な図形を提供し、それらを纏めて管理する必要があるが、その中から特定の型のみを選びだす」場合などがそれに当たる。
抽象化した集合内から、特定の具体的な型のみを取り出す場合、と言い換えてもよい。
こういった場合、何か良い方法が無いものかと思案していたが、アジャイル開発の奥義のOCPの項目で、似たような事例があった*1。手元に書籍が無いので詳しく書けないが、何を抽象化するかが大事か、とのことであった(はず)。


ということで、久々に抽象→具象のダウンキャストが必要になったので、型変換の汎用化について簡単に実装してみた。
ソースのベースはid:rf0444*2の記述したものを参考にしています。

JavaGenericsとinstanceof

具体的にC++のdynamic_castみたいに使用したかったのだけれど、無理みたい。

// こんな感じで使いたかった。
// というか、多分このように記述出来ないような気がする。
// dynamic_castは、sが変換できればその型に変換したものを、
// 変換できなければnullを返すメソッド
Shape s = new Point();
Point p = dynamic_cast<Point>(s);

これが出来れば嬉しかったんだけれどなぁ。
あと、instanceof演算子の右辺にGenericsの型は指定できません。具体的には、以下のコードはエラー。

// TはGenericsの型
if( s instanceof T ) {
    ...
}

Class型の使用

id:rf0444が使っていたのを見て思い出したけれど、[具象クラス名.class]で、そのクラスの型を取得出来る。なので、コレを使えば、C#のAS演算子風のstaticメソッドが作れる。引数を2つ指定するのが微妙なので、フルーエント風(?)な実装もしてみた。

// staticメソッドの実装
package util;

public final class CastUtil {
	
	private CastUtil(){}
	
	// 普通にAS
	public static <T> T as(Object obj, Class<T> clazz) {
		if( !clazz.isInstance(obj) ) {
			return null;
		}
		return clazz.cast(obj);
	}
	
	// フルーエントっぽく
	public static Casting cast(Object obj) {
		return new Casting(obj);
	}
	
	public static final class Casting {
		
		Object obj;
		
		Casting(Object obj) {
			this.obj = obj;
		}
		
		public <T> T as(Class<T> clazz) {
			if( !clazz.isInstance(obj) ) {
				return null;
			}
			return clazz.cast(obj);
		}
	}
}
// 実際に使ってみる。
import static util.CastUtil.as;
import static util.CastUtil.cast;

import java.util.ArrayList;

public final class CastUtilTest {
	public static void main(String[] args) {
		new CastUtilTest().run();
	}
	
	ArrayList<Shape> list = new ArrayList<Shape>();

	private void run() {
		presetLists();
		int pc = 0, lc = 0;
		for(Shape s : list) {
			// staticメソッド風
			Point p = as(s, Point.class);
			if( p != null ) pc++;
			// フルーエント風?
			Line l = cast(s).as(Line.class);
			if( l != null ) lc++;
		}
		System.out.println("point count = "+pc);
		System.out.println("line count = "+lc);
	}

	private void presetLists() {
		list.add(new Point());
		list.add(new Line());
		list.add(new Point());
		list.add(new Line());
		list.add(new Line());
	}
}

interface Shape {}
class Point implements Shape {}
class Line implements Shape {}

実行結果
point count = 2
line count = 3

そこそこ汎用的にはなっている、様な気がする。
何か改善点とか、この手の面白い書き方があれば教えて下さいませ。


# Commonsとか探すと同じような機能が転がっていそうな気もしたけど、とりあえず書いて見た。

*1:事例は教えてもらったのだけれど。

*2:筆無精←自分が記事を書いた理由