java-ja温泉2日目の夕食。 @yoshiori がpythonのワンライナの楽しさを得々と語っていた。
@yoshiori「Brainf*ck を Python-oneliner にコンパイルする Python-onelinerを書いたけど全ッ然反応がなかった。こんなに面白いのに!」
@yamashiro「だって分かりにくいもん」
西尾先生が通常ワンライナではtry-catchが使えないけど子プロセス立ち上げて例外を出力してパースすればエラー処理ができるとか(http://www.nishiohirokazu.org/blog/2006/08/python_12.html参照)そんな話で盛り上がる中、
@nagise「Javaでセミコロンなしでプログラムが書けるような気がしてきた」
Javaの場合、普通にセミコロン(;)でマルチステートメントにかけるのでただ1行にしようというなら改行を削るだけになってしまう。pythonのワンライナと張り合うならセミコロンを使わないという制約を課すのが妥当だろう。
Hello world
まずは基本と言えるHello worldを書いてみよう。ここで問題になるのはSystem.out.println()だ。普通に呼び出そうとするとセミコロンが避けられない。戻り値がvoidでなければifの()に閉じ込めるなどすればセミコロンを必要としないというのに。たとえばhoge()メソッドを呼び出す場合
if (hoge() == null) {}
とすればセミコロンなしでメソッド呼び出しを行うことができる。ところが戻り値がvoidの場合、この手法が使えない。コンパイルエラーになってしまう。
そこでリフレクションでメソッド呼び出しを行う。
public class HelloWorld { public static void main(String[] args) throws Exception { if (java.io.PrintStream.class.getMethod("println", String.class) .invoke(System.out, "Hello world") == null) {} } }
package文が使えないのでデフォルトパッケージを用いる。import文も使えないのでjava.io.PrintStreamというようにフルパッケージでの記述を常に行わなくてはならない。Method#invoke()であればvoidメソッドの呼び出しでもObject型の戻り値が戻ることになるので式*1として呼び出しが行える。
FizzBuzz
よし、標準出力が使えることがわかったのでFizzBuzz問題を解いてみよう。ワンライナは基本的に文が書けないので式だけでロジックを書かなくてはいけない。となれば関数型言語でのFizzBuzzのようにメソッドの再帰で表現するのが適当だろう。
public class FizzBuzz { public static void main(String[] args) throws Exception{ if (FizzBuzz.class.getMethod( "hoge", java.lang.reflect.Method.class, int.class, int.class).invoke( null, java.io.PrintStream.class.getMethod("println", String.class), 1, 100) == null) {} } public static void hoge(java.lang.reflect.Method m, int i, int max) throws Exception { if (i % 15 == 0) { if (m.invoke(System.out, "FIZZBUZZ") == null) {} } else if (i % 3 == 0) { if (m.invoke(System.out, "FIZZ") == null) {} } else if (i % 5 == 0) { if (m.invoke(System.out, "BUZZ") == null) {} } else { if (m.invoke(System.out, "" + i) == null) {} } if (i < max && FizzBuzz.class.getMethod("hoge", java.lang.reflect.Method.class, int.class, int.class).invoke(null, m, ++i, max) == null) {} } }
セミコロンなしには通常の方法では変数の宣言ができない。だが、メソッドの引数としてなら変数宣言がセミコロンなしで行うことができる。関数型言語のようにループを再帰処理に展開することで変数宣言を回避したわけだ。
変数の宣言
変数の宣言は通常では行えない。どうにかすることはできないものか。変数を宣言したいがためにメソッドを宣言し、リフレクションで呼び出すというのはあまり美しくない。
そこで拡張for文を用いる。
for (int i : new int[] { 0 }) { }
このようにすることでこの拡張for文の中では変数iが利用可能になる。初期値はnew int[] { 0 }で与えることができる。この手法を使うことでローカル変数をいくらでも自在に宣言することが可能となった。これでFizzBuzzをJavaでおなじみのループを使った書き方に書き直すことができる。
public class FizzBuzzLoop { public static void main(String[] args) throws Exception { for (java.lang.reflect.Method m : new java.lang.reflect.Method[] { java.io.PrintStream.class .getMethod("println", String.class) }) { for (int i : new int[] { 0 }) { for (int x : new int[100]) { if (i % 15 == 0) { if (m.invoke(System.out, "FIZZBUZZ") == null) {} } else if (i % 3 == 0) { if (m.invoke(System.out, "FIZZ") == null) {} } else if (i % 5 == 0) { if (m.invoke(System.out, "BUZZ") == null) {} } else { if (m.invoke(System.out, "" + i) == null) {} } if (++i == 0) {} } } } } }
これでずいぶん読みやすくなった。
メソッド呼出
メソッド呼び出しがいちいちリフレクションなのがどうも美しくない。自分でメソッドを定義する場合、戻り値がvoidだと呼び出し元でセミコロンが必要となるのでNGで、戻り値があるメソッドを宣言するとreturnがセミコロンなしに書けないのでNGとなる。
匿名クラスを宣言することはできるが、メソッドの代用にはならない。初期化ブロックはセミコロンなしで書けるしnewも呼び出せるが、値を返せないし、同一インスタンスに対して繰り返し呼び出すことができない。
そこでHashMapを拡張した匿名クラスを活用する。
for (Map map : new HashMap[]{ new HashMap() { public void putAll(Map m) { // ここに実装を書く } } }) { // ここでオブジェクトを使用する }
Map#putAll(Map)メソッドをオーバーライドして書きたい処理を記述する。引数にMapを取るので、キーをあらかじめ約束しておくことで引数の代わりにする。戻り値はどうするかというと、thisにput()する。
public void putAll(Map m) { // 引数の値を2倍にして返す if (this.put("ret", ((Integer) m.get("in")) * 2) == null) {} }
こうすることでforのスコープ内ではputAll()で呼び出しをしてmap.get("ret")で値を取得することができる。メソッド呼び出しができるようになった。というか、プロトタイプ型オブジェクト指向ができる。
,ィィr-- ..__、j ル! { `ヽ, ∧ N { l ` ,、 i _|\/ ∨ ∨ ゝヽ _,,ィjjハ、 | \ `ニr‐tミ-rr‐tュ<≧rヘ > {___,リ ヽ二´ノ }ソ ∠ Javaプログラムにセミコロンは必要なかったんだよ!! '、 `,-_-ュ u /| ∠ ヽ`┴ ' //l\ |/\∧ / --─‐ァ'| `ニ--‐'´ / |`ー ..__ `´ く__レ1;';';';>、 / __ | ,=、 ___ 「 ∧ 7;';';'| ヽ/ _,|‐、|」 |L..! {L..l )) | |::.V;';';';'| /.:.|トl`´.! l _,,,l | _,,| , -, ! |:.:.:l;;';';';'|/.:.:.:||=|=; | | | | .l / 〃 )) l |:.:.:.:l;';';'/.:.:.:.:| ! ヽ \!‐=:l/ `:lj 7 | |:.:.:.:.l;'/.:.:.:.:.:.! ヽ:::\:: ::::| ::l /
追記
オブジェクト指向の部分はセミコロンレスJavaの構造化&オブジェクト指向 - プログラマーの脳みそで詳しく書いたので参考にしてください
*1:式と文はプログラム言語の構文としては別のもので、端的に言うと値が返るのが式、返らないのが文。式と文ではかける場所が異なる