JavaOne Tokyo 2012でLTをしてきました。内容は寺田さんの要請でSemicolonless Java(セミコロンレス・ジャバ)です。
当日は風邪で喉をやられたので音声が聞き取りにくかったと思います。申し訳ないです。公式のフォトライブラリーがあるので当日の会場の雰囲気がいくらか分かるかと思います。僕も写ってます。
LT資料はそのうち公式ページで見れるようになると思います。が、LT用なので資料だけ見てもよく分からないと思います。というわけで解説をしておきます。
Hello world
通常System.out.println();を利用しますが、このセミコロンをどうやって回避するのか。
public class HelloWorld { public static void main(String[] args) throws Exception { if(System.out.printf("Hello world.")==null){} } }
System.out.println()は戻り値がないので呼び出しが困難(後述)ですが、printf()であればPrintStreamを返すのでこれを適当な値(ここではnull値)と比較してif文に押し込めることでセミコロンを取り除くことができます。
戻り値のあるメソッドは呼び出すのは簡単ですが、セミコロンレスではreturnを記述できないため宣言することが不可能です。既存のJava APIのメソッドを呼ぶことしか出来ません。
戻り値がvoidのメソッドは通常の呼び方では呼び出せませんが、リフレクションを利用することで呼び出すことができます。
if (java.io.PrintStream.class.getMethod( "println", String.class).invoke( System.out, "Hello world.") == null){}
ただし、このリフレクションによるメソッドコールは多用しないほうが美しいとされます。
FizzBuzz
for(int i=1; i<100; i++) {}
ここでforではなくwhileを利用することもできますが、
いずれにせよループカウンタを宣言しようとするとセミコロンが必要になります。
どうするのか?
public class FizzBuzz { public static void main(String[] args) { for (int i : new int[] { 0 }) { while (++i <= 100) { if (i % 3 == 0 && i % 5 == 0) { if (null==System.out.printf("FizzBuzz\n")){} } else if (i % 3 == 0) { if (null==System.out.printf("Fizz\n")){} } else if (i % 5 == 0) { if (null==System.out.printf("Buzz\n")){} } else { if (null==System.out.printf("%d\n", i)){} } } } } }
変数の宣言をしたい場合はfor-each構文を利用します。
for (int i : new int[] { 0 }) { // ... }
クイックソート
クイックソートを作る場合、再帰呼び出しを行うのが一般的です。戻り値がvoidのメソッドは呼び出しにリフレクションが必要になりますし、戻り値があるメソッドはセミコロンなしには宣言できません。メソッドの代わりになるものがなくては構造化プログラミングがままなりませんね。
どうすればいいのか?
public class Sort { public static void main(String[] args) { for (java.util.List<Integer> list : new java.util.ArrayList<java.util.List<Integer>>() {{ if (add(new java.util.ArrayList<Integer>( java.util.Arrays.asList( 5, 3, 9, 0, 1, 8, 7, 2, 4, 6)) { })) {} }}) { if (System.out.printf(list + "\n") == null) {} if (new QuickSort(list, 0, list.size() - 1) == null) {} if (System.out.printf("" + list) == null) {} } } public static class QuickSort { public QuickSort(java.util.List<Integer> list, int left, int right) { if (left <= right) { for (int p : new int[] { list.get((left + right) / 2) }) { for (int l : new int[] { left }) { for (int r : new int[] { right }) { while (l <= r) { while (list.get(l) < p) { if (++l == 0) {} } while (list.get(r) > p) { if (--r == 0) {} } if (l <= r) { if (list.set(l, list.set(r, list.get(l))) == null) {} if (++l == 0) {} if (--r == 0) {} } } if (new QuickSort(list, left, r) == null) {} if (new QuickSort(list, l, right) == null) {} }}} } } } }
メソッドの代わりにコンストラクタを使いましょう。
こうすることで呼び出しが簡単になります。
Pointクラスを作る
X,Y座標を持ち、距離を取得する機能を持った“Point クラス”を作ってみましょう。オブジェクト指向プログラミングしたいですもんね。
セミコロンを用いないとクラスにフィールドを宣言することができません。ローカル変数はfor-each文で代用することが出来ましたがどうすればフィールド宣言を代替できるのか?
また、どうすれば値を戻すメソッドを代替できるのか?
public class Point extends java.util.HashMap<Point.FieldKey, Double> { enum FieldKey { X, Y, } public Point(double x, double y) { if(this.put(FieldKey.X, x)==null){} if(this.put(FieldKey.Y, y)==null){} } public class GetX extends java.util.Stack<Double> {{ if (push(Point.this.get(FieldKey.X))==null){} }} public class GetY extends java.util.Stack<Double> {{ if (push(Point.this.get(FieldKey.Y))==null){} }} public class GetDistance extends java.util.Stack<Double> { public GetDistance(double x, double y) { if (push(Math.sqrt( Math.pow((Point.this.get(FieldKey.X) - x), 2) +Math.pow((Point.this.get(FieldKey.Y) - y), 2)))==null){} } } public static void main(String[] args) { for (Point p : new Point[]{new Point(3, 4)}) { if (System.out.printf("x: %e, y: %e, distance: %e", p.new GetX().pop(), p.new GetY().pop(), p.new GetDistance(0, 0).pop())==null){} } } }
フィールド宣言が行えないのでHashMapを継承することでフィールドの代わりとします。キーは列挙型にしておくとよいでしょう。値はここではDouble型にしていますが
一般的にフィールドは同じクラス内でもいろんな型を用いますからObject型としておきキャストすることになります。
戻り値を戻すメソッドの代替ですが、Stackを継承した内部クラスを作ることで解決できます。
public class GetX extends java.util.Stack<Double> {{ if (push(Point.this.get(FieldKey.X))==null){} }}
このようにしておくと呼び出し側では
p.new GetX().pop()
とすることで値を得ることができます。
内部クラスをnewするときは 外部クラスの変数.new とします。
詳しくは別記事を参照。
型安全
さて、ここまでが前ふりですね。
フィールドの代替としてHashMapを継承してObject型からキャストするということでしたがやはりJavaを使うからには型安全にしたいですよね。
これこそが2012年版のSemicolonless Javaのハイライトです。
Mapはキーごとに値の型を変えることができません。
HashMap<Key, Object> map = new HashMap<>(); enum Key{ NAME, AGE, } String name = (String)map.get(Key.NAME); int age = (Integer)map.get(Key.AGE);
とりあえず、キーを列挙からTypesafe-enumパターンを利用してキーごとに値の型を持たせてみましょう。
public final class Key<V>{ public static final Key<String> NAME = new Key<>(); public static final Key<Integer> AGE = new Key<>(); }
すると、Mapのフィールド
HashMap<Key<?>, Object> map = new HashMap<>();
があったとして、
public <T> T get(Key<T> key) { return (T)map.get(key); }
と書くことで
String name = get(Key.NAME);
int age = get(Key.AGE);
というように型安全にすることができます。
ところが、Semicolonless Java ではフィールドは使えないのでこのような機能性をもったMapのラッパークラスを作り、それをextendsして使いたい。
とりあえず単純に
public class KeyValue<K> { KeyValue<K, Object> map; public <T> T get(K<T> key) { // Compile error ! return map.get(key); } }
というようにクラスに括りだすとK
そこでまず
public class Group { /** singleton */ private static final Group g = new Group(); public class Key<T> {} public static final Key<String> NAME = g.new Key<>(); public static final Key<Integer> AGE = g.new Key<>(); }
というようにキーをGroup型とその内部クラスKey型という2階層構造にします。そしてこのGroup型を抽象化して具象型と分離します
public abstract class Group<G extends Group<G>> { public class Key<T> {} } public class PersonKey extends Group<PersonKey> { /** singleton */ private static final PersonKey g = new PersonKey(); public static final Key<String> NAME = g.new Key<>(); public static final Key<Integer> AGE = g.new Key<>(); }
このGroup型を先ほどのK
public class KeyValue<K extends KeyValue.Group<K>> { public static abstract class Group<G extends Group<G>> { public class Key<T> {} } HashMap<Group<K>.Key<?>, Object> map = new HashMap<>(); @SuppressWarnings("unchecked") public <T> T get(Group<K>.Key<T> key) { return (T)map.get(key); } }
このKeyValueクラスを使用する場合は
KeyValue<PersonKey> map = new KeyValue<>(); String name = map.get(PersonKey.NAME); int age = map.get(PersonKey.AGE);
という感じになります。キーごとに戻り値の型が定まりました。これは以前にJavaによる高階型変数の実装 - プログラマーの脳みそというエントリで紹介したジェネリクスのテクニックです。
この技術をSemicolonless Javaに応用してみましょう。
Semicolonless化
まずキーの列挙ですが、static finalなフィールドを作って定数とするのがそもそもSemicolonlessには不可能なので、定数の代わりに内部クラスを使います。
public class PersonKey extends KeyValue.Group<PersonKey> { class Name extends PersonKey.Key<String> {} class Age extends PersonKey.Key<Integer> {} }
ここでextendsしているPersonKey.Keyクラスですがこれは親のクラスで宣言されている内部クラスが実体です。
public static abstract class Group<S extends Group<S>> { public class Key<T> {} }
ただ、このPersonKey.NameやAgeをMapのキーとして使ってもうまくMapが機能しません。
MapのキーにするためにはhashCode()とequals()を
オーバーライドしなければいけないのでしたね。ところがSemicolonlessにこれら値を返すメソッドのオーバーライドはできないのでした。
そこで、すでにあるものを使えばいいということでjava.awt.Pointなどの既存のクラスを利用します。
public static abstract class Group<S extends Group<S>> { public class Key<T> extends java.awt.Point {} }
こうすることでName型とAge型はどのインスタンスも同値と判断されるクラスになりました。Mapのキーとして使えるようになりましたね。
あとはメソッドをSemicolonless化して
public class KeyValue<G extends KeyValue.Group<G>> extends java.util.HashMap<Object, Object>{ public static abstract class Group<S extends Group<S>> { public class Key<T> extends java.awt.Point {} } public class Put { public <T> Put(Group<G>.Key<T> key, T value){ if (KeyValue.this.put(key, value) == null){} } } public class Get<T> extends java.util.Stack<T> { @SuppressWarnings("unchecked") public Get(Group<G>.Key<T> key) { if (this.push((T)KeyValue.this.get(key))==null){} } } }
というKeyValueクラスを作ります。これがキーによって値の型がかわるタイプセーフでSemicolonlessなMapの代替型です。
これを継承したクラスを作り
public class Main extends KeyValue<PersonKey>{ public static void main(String[] args) { if (new Main()==null){} } public Main() { if (this.new Put(new PersonKey().new Name(), "なぎせ ゆうき") == null){} if (this.new Put(new PersonKey().new Age(), 0x20) == null){} for(String name : new String[]{ this.new Get<>(new PersonKey().new Name()).pop() }) { for(int age : new int[]{ this.new Get<>(new PersonKey().new Age()).pop() }) { if (System.out.printf(name+" (%d)", age)==null){} }} } }
というように利用します。
これでタイプセーフなフィールド宣言の代替ができるようになりました。