Semicolonless Java 入門
これは coins Advent Calendar 11日目の記事です。
10日目の記事は 自炊しろITF.生 - 気合でなんとか です。
外食も最高なんですが、自炊の良いところはたまにありえん美味しいものが出来上がってしまうことがあるということですね。
多分自炊してる人は経験あると思います。でも、味付け難しい。
空いてたので2記事目を書くことにしました。
ところで、あなたはJavaでは絶対セミコロンを書かなければいけないと思っていませんか?
しかしそんな必要はありません。
今日は あなたにSemicolonless Javaの世界を紹介します。
Semicolonless Javaのルール
セミコロンを用いてはいけない
ユニコードエスケープは不可
Java SEの標準APIだけを使う
Semicolonless Java 2012 - プログラマーの脳みそ
簡単ですね。
Hello, World!
Java8時代のSemicolonless JavaでHello, World!を実行するには幾つか方法がありますが、今回は2つ紹介します。
if (java.util.concurrent.Executors.callable(() -> System.out.println("Hello, World!")).call() == null) {}
1つ目
1つ目はif文と java.util.concurent.Executors.callable
の合わせ技です。
if文の条件式の中でメソッドを呼び出し、その戻り値と何らかの値とを比較する手法はSemicolonless Javaでは昔から使われている定番的な手法です。
しかし、 java.io.PrintStream#println
の戻り値は void
なため、何らかの値と比較できず、この手法は利用できません。
そこで、Executors.callable
の出番です。 callable
に Hello, World! を標準出力に出力する Runnable
を渡して Callable<Object>
オブジェクトを生成し、 Callable#call
で渡した Runnable
を実行します。
Callable#call
は Runnable
の実行後にその結果を自身のオブジェクトの型パラメータで返します。ここでは Callable
の型パラメータは Object
なので戻り値は Object
型のオブジェクトとなりますが、
Executors.callable
の戻り値 Callable
オブジェクトは null
を返す実装になっています。
2つ目
try (java.util.stream.Stream s = Stream.of().onClose(() -> System.out.println("Hello, World!"))) {}
2つ目は try-with-resources
文と BaseStream#onClose
の合わせ技です。
try-with-resources
文は、宣言したリソース( AutoClosable
を実装しているクラスのオブジェクト)をtry文が終了したら自動で閉じる、つまり AutoClosable#close
を呼び出すというものです。
Stream
のスーパークラスである BaseStream
は AutoClosable
を実装していて、 BaseStream#close
が呼び出された時に、 BaseStream#onClose
で引き渡した Runnable
を実行します。
変数宣言
Java8以前のSemicolonless Javaでは変数宣言に拡張for文が用いられていましたが、Java8では Stream
と Stream#peek
の合わせ技を用いることがあります。
if (Stream.of("James").peek(firstName -> Stream.of("Gosling").forEach(lastName -> System.out.println(firstName + " " + lastName))).toArray() == null ) {}
まず、何らかの方法で宣言したい変数の値を持つ、一つしか値を持たないStreamを作ります。 Stream.of
を利用すると簡単に Stream
が作れますね。
さて、この Stream
に対して Stream#forEach
を呼んで、その中で変数を利用したいところですが、 Stream#forEach
の戻り値は void
です。前述のvoidを回避する手法を用いても良いのですが、今回は Stream#peek
を使います。
Stream#peek
は Stream
をそのまま返しますが、 Stream
の各要素に対して、引数として渡した非干渉アクションを実行します。主にデバッグ用途で利用されますが、今回はこれを悪用利用しました。
ところでラムダ式では文をブロックを用いず1行だけ書く場合に限り、セミコロンを省略することができます。つまり、その中では任意のメソッドを呼ぶことができるわけです。今回はもう一つ変数を宣言して、 Stream#forEach
でこれら2つの変数を利用しています。
Fizz Buzz
さて、入門記事ということで、Fizz Buzz*1までやりましょう。
if (java.util.stream.IntStream.rangeClosed(0, 100) .mapToObj(n -> n % 15 == 0 ? "FizzBuzz" : n % 3 == 0 ? "Fizz" : n % 5 == 0 ? "Buzz" : String.valueOf(n)) .peek(System.out::println) .toArray() == null) { }
まず、 IntStream.rangeClosed
で0から100までの Integer
型の Stream
を生成します。
次に、 Stream#mapToObj
を利用して、その各要素を3項演算子を用い、適切な String
オブジェクトに変換します。
そして、 Stream#peek
を呼び出して標準出力に出力し、最後に先述した手法を用いてセミコロンを排除しコードを実行します。
簡単ですね。
Java 8になってからSemicolonless Javaがとても簡単になってしまいました。なので、Java 7以下縛りとか、どんどん縛りをきつくしていくと良いと思います。
参考までに、昔Java 7でSemicolonless Java入門したときのコードを貼っておきます。
縦書き俳句プログラミング in Semicolonless Java. · GitHub
Java 9時代のSemicolonless Java
$ jshell
jshell> System.out.println("Hello, World!")
Hello, World!
なんでや!