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#callRunnable の実行後にその結果を自身のオブジェクトの型パラメータで返します。ここでは 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スーパークラスである BaseStreamAutoClosable を実装していて、 BaseStream#close が呼び出された時に、 BaseStream#onClose で引き渡した Runnable を実行します。

変数宣言

Java8以前のSemicolonless Javaでは変数宣言に拡張for文が用いられていましたが、Java8では StreamStream#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#peekStream をそのまま返しますが、 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!

なんでや!

参考