mike-neckのブログ

JavaかJavaFXかJavaEE(なんかJava8が多め)

JEP-330 Single-FIle Source-Code Programs って何? Java ファイルが java コマンドで実行できるってホント?! shebang でどう指定するの?調べてみた!

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

f:id:mike_neck:20181019112748p:plain

(『古い機能』という表現はその手の界隈に人達への忖度表現)


TL;DR ってなんだ???

  • Java 11 にとりこまれた JEP-330 は 1つの java ファイルを java コマンドで実行できる仕組み
  • java という拡張子でなければ、ファイルの先頭に shebang をつけて実行可能ファイルとして実行できる
  • 内部的には javac に依存しているので、カスタムランタイムなどでは実行できない
  • 外部の jar も利用できる
  • shebangclasspath の指定をする場合は順番に要注意

JEP-330(Launch Single-File Source-Code Programs)

JEP-330 では一つのソースコードファイルだけであれば、 java コマンドで実行できるようにしてしまう機能です。

JEP 330: Launch Single-File Source-Code Programs

d.hatena.ne.jp

物は試しに次のようなファイル Hello.java を作ります。

class Hello {
  public static void main(String... args) { System.out.println("Hello"); }
}

このファイルに対して、次のコマンドを実行します。

java Hello.java

結果のスクショをどうぞ。

f:id:mike_neck:20181208130230p:plain
java ファイルを実行できる

ちなみに、上記コマンドの正式な実行の仕方は以下の通りだそうです。

java --source 11 Hello.java

もちろん、この 11Java 11 の 11 で、 11 よりもやや小さい値でも実行できるようです。

f:id:mike_neck:20181208131051p:plain
--source 8

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

f:id:mike_neck:20181208131206p:plain
6 の場合は警告が大量に…

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

f:id:mike_neck:20181208131324p:plain
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

では実行してみましょう。

f:id:mike_neck:20181208131706p:plain
失敗した?!

どうやら、 先程のような実行するスクリプトとは認識せずに、 コンパイルするファイルとして認識されてしまった模様です。

このコードの名前を少し変えてみましょう。

mv Hello.java hello
./hello

実行結果はこちらです。

f:id:mike_neck:20181208132114p:plain
実行できた!

どうやら、拡張子が .java になっていると実行ファイルとみなされないようです。


java コマンドだけあればいい?

ここで気になるのが、 この機能は java コマンドだけあればいけるのかということです。たとえば、前回のエントリーのようにカスタムランタイムを作った場合に、実行する内容がシングルファイルで書ける内容であればコンパイルせずに lambda に乗せて実行できるのでしょうか?

mike-neck.hatenadiary.com

試してみます。次のコマンドで作ったカスタムランタイムを shebang で指定してみます。

jlink \
  --compress=2 \
  --module-path $JAVA_HOME/mods \
  --add-modules java.base,java.net.http \
  --output runtime

f:id:mike_neck:20181208134421p:plain
実行できませんでした!

先程のツイートにきつねくんから回答をもらっていて、内部的には javac を実行しているとのことです。 java ファイルとカスタムランタイムをリリースして Java で Script なで JavaS(以下自粛) という淡い夢は儚くも散りました。


ライブラリー使えるの?

次に気になるのが、ライブラリーがある場合はどうなのでしょう?

これも試してみましょう。

ここでは AssertJ を使ってみます。

先程のコードも次のように書き直します。 falseisTrue なわけがないので、落ちるテストです。

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

実行結果はこちら。

f:id:mike_neck:20181208140023p:plain
外部の jar も取り込める


shebang の中で classpath 指定したい

さらにもう一歩進んで、 shebang の中で jar ファイルを指定してみましょう。先程の Assert.javaassert に改名して、 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();
  }
}

こちらを実行したらどうなるでしょうか?

f:id:mike_neck:20181208140845p:plain
shebang の中で classpath を指定する

うまく実行できるようです。なお、 shebang の中での --class-path--source の順番を入れ替えてみました。すると…

f:id:mike_neck:20181208141142p:plain
shebang に書く順番を間違えると実行できない

実行できなくなるようです。


JEP-330 の Single-File Source-Code Programs について紹介してきましたがいかがでしたでしょうか。

これまでちょっとしたアプリケーションも javac でコンパイルしなければならなかったので、ずいぶんと便利になりましたね。

もし何かぬけもれがあるようでしたら、ぜひ教えてくださいね。

今回も最後までお読みいただきありがとうございました。