Java11 で取り込まれている JEP-330 すでにきしださんがブログにしている古い機能ですが、自分用のメモ。

(『古い機能』という表現はその手の界隈に人達への忖度表現)
TL;DR ってなんだ???
- Java 11 にとりこまれた JEP-330 は 1つの java ファイルを
javaコマンドで実行できる仕組み javaという拡張子でなければ、ファイルの先頭に shebang をつけて実行可能ファイルとして実行できる- 内部的には
javacに依存しているので、カスタムランタイムなどでは実行できない - 外部の jar も利用できる
- shebang に classpath の指定をする場合は順番に要注意
JEP-330(Launch Single-File Source-Code Programs)
JEP-330 では一つのソースコードファイルだけであれば、 java コマンドで実行できるようにしてしまう機能です。
JEP 330: Launch Single-File Source-Code Programs
物は試しに次のようなファイル Hello.java を作ります。
class Hello { public static void main(String... args) { System.out.println("Hello"); } }
このファイルに対して、次のコマンドを実行します。
java Hello.java
結果のスクショをどうぞ。

ちなみに、上記コマンドの正式な実行の仕方は以下の通りだそうです。
java --source 11 Hello.java
もちろん、この 11 は Java 11 の 11 で、 11 よりもやや小さい値でも実行できるようです。

6 を指定すると警告が大量に出ますが、実行できるようです。

サポートの終わっている 5 では残念ながら実行できません。

shebang のついてる java
java コマンドで java ファイルを一発で実行できるということは、 java ファイルを解釈するインタプリターとして java コマンドが使えるということなので、次のようなファイルでも valid な java ファイルと言えます。そこで、先程の Hello.java の先頭行に shebang をつけます。
#!/usr/bin/env java --source 11 class Hello { public static void main(String... args) { System.out.println("Hello"); } }
さらに、実行可能にします。
chmod +x Hello.java
では実行してみましょう。

どうやら、 先程のような実行するスクリプトとは認識せずに、 コンパイルするファイルとして認識されてしまった模様です。
このコードの名前を少し変えてみましょう。
mv Hello.java hello ./hello
実行結果はこちらです。

どうやら、拡張子が .java になっていると実行ファイルとみなされないようです。
これ、拡張子 java がついていると実行できないんですね! pic.twitter.com/Y6xU9pP4wr
— 石◯王 もちだ (@mike_neck) 2018年12月7日
java コマンドだけあればいい?
ここで気になるのが、 この機能は java コマンドだけあればいけるのかということです。たとえば、前回のエントリーのようにカスタムランタイムを作った場合に、実行する内容がシングルファイルで書ける内容であればコンパイルせずに lambda に乗せて実行できるのでしょうか?
JEP 330 って仕組み的にはどうなってんだろ? javac なくても実行可能?なわけないよね…
— 石◯王 もちだ (@mike_neck) 2018年12月7日
試してみます。次のコマンドで作ったカスタムランタイムを shebang で指定してみます。
jlink \ --compress=2 \ --module-path $JAVA_HOME/mods \ --add-modules java.base,java.net.http \ --output runtime

先程のツイートにきつねくんから回答をもらっていて、内部的には javac を実行しているとのことです。 java ファイルとカスタムランタイムをリリースして Java で Script なで JavaS(以下自粛) という淡い夢は儚くも散りました。
ライブラリー使えるの?
次に気になるのが、ライブラリーがある場合はどうなのでしょう?
これも試してみましょう。
ここでは AssertJ を使ってみます。
先程のコードも次のように書き直します。 false が isTrue なわけがないので、落ちるテストです。
import static org.assertj.core.api.Assertions.*; public class Assert { public static void main(String... args) { assertThat(false).isTrue(); } }
次のコマンドで実行します。
java \ --class-path assertj-core-3.9.1.jar \ Assert.java
実行結果はこちら。

shebang の中で classpath 指定したい
さらにもう一歩進んで、 shebang の中で jar ファイルを指定してみましょう。先程の Assert.java を assert に改名して、 shebang をつけます。
#!/usr/bin/env java --class-path assertj-core-3.9.1.jar --source 11 import static org.assertj.core.api.Assertions.*; public class Assert { public static void main(String... args) { assertThat(false).isTrue(); } }
こちらを実行したらどうなるでしょうか?
うまく実行できるようです。なお、 shebang の中での --class-path と --source の順番を入れ替えてみました。すると…

実行できなくなるようです。
JEP-330 の Single-File Source-Code Programs について紹介してきましたがいかがでしたでしょうか。
これまでちょっとしたアプリケーションも javac でコンパイルしなければならなかったので、ずいぶんと便利になりましたね。
もし何かぬけもれがあるようでしたら、ぜひ教えてくださいね。
今回も最後までお読みいただきありがとうございました。
