JavaでC#のas演算子の様なものを実装してみた
どう考えてもなかなか抽象化が難しく、instanceofなどの動的型変換を使った方が有効な場合もある。
例えば、「様々な図形を提供し、それらを纏めて管理する必要があるが、その中から特定の型のみを選びだす」場合などがそれに当たる。
抽象化した集合内から、特定の具体的な型のみを取り出す場合、と言い換えてもよい。
こういった場合、何か良い方法が無いものかと思案していたが、アジャイル開発の奥義のOCPの項目で、似たような事例があった*1。手元に書籍が無いので詳しく書けないが、何を抽象化するかが大事か、とのことであった(はず)。
ということで、久々に抽象→具象のダウンキャストが必要になったので、型変換の汎用化について簡単に実装してみた。
ソースのベースはid:rf0444*2の記述したものを参考にしています。
JavaとGenericsと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とか探すと同じような機能が転がっていそうな気もしたけど、とりあえず書いて見た。