これは Java スクリプトですか？

JavaScript の記事をお探しの皆様におかれましては、お役に立つような内容ではございませんので、ブラウザの戻るをお願いいたします。

また、かなり濃いめの Java ジョークとなっておりますので、Java 初心者の皆様におかれましては、真に受けないようにお願いいたします。


# OpenJDK 11 からは Java ファイルを直接実行できる

つい最近知ったのですが、OpenJDK 11 で [JEP 330: Launch Single-File Source-Code Programs](https://openjdk.java.net/jeps/330) が導入され、単一ファイルの Java プログラムならば、javac などでコンパイルしなくても実行できるようになっています。

* [ソース・ファイル・モードで単一ファイルのソース・コードのプログラムを起動する方法](https://docs.oracle.com/javase/jp/15/docs/specs/man/java.html#using-source-file-mode-to-launch-single-file-source-code-programs)
* [JEP-330 Single-FIle Source-Code Programs って何？ Java ファイルが java コマンドで実行できるってホント？！ shebang でどう指定するの？調べてみた！](https://mike-neck.hatenadiary.com/entry/2018/12/10/063000)
* [【Modern Java】JEP 330 Launch Single-File Source-Code Programs](https://blog1.mammb.com/entry/2020/08/07/090000)

シェルでは様々なスクリプトを使いますよね。実行可能属性のついたスクリプト ファイルを実行するとき、Unix では [shebang](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%90%E3%83%B3_(Unix)) の指定でそのスクリプトを解釈するプログラムが決まります。`#!/bin/sh` や `#!/bin/bash` のようにシェルが指定されたものは「シェル スクリプト」と呼ばれ、`#!/usr/bin/env perl` などのスクリプト言語が指定された場合は「Perl スクリプト」のように言語名で呼ばれます。ならば、**`#!/usr/bin/env java` が指定されたものは「Java スクリプト」**なのではないでしょうか！[^1]

macOS にインストールした OpenJDK 15 で試してみましたが、前述のブログ記事で mike_neck さんによりハマる箇所が特定済みだったおかげで、すんなり実行できました。これは面白いことになりそうです。


# Java スクリプトのファイル形式

とはいえまだ仕様を把握できていませんでしたので、まずは真面目に JEP 330 を確認します。Oracle の文書を参照しても、結局 JEP 330 を見るように書かれていました。

ふつうスクリプトは shebang で始まるものだと思っていますので、この記事では JEP 330 でいう shebang file のことを Java スクリプトと呼んでいます。

> \#!/path/to/java --source version

1行目に上記のような shebang を書く、と JEP 330 にはあります。ですが、`#!/usr/bin/env java --source 11` のように env を介したほうが移植性が高くなると思います。

> In a shebang file, the first two bytes must be 0x23 0x21

そして shebang の `#!` は 0x23 0x21 というバイト列でなくてはなりません。Java は UTF-16 で書きたいという方も、最初の2バイトだけは UTF-8 のように書かなければなりません。

> \* The name of the shebang file does not follow the standard naming conventions for Java source files.

これは要は、`public class Hello` でも Hello.java ではなく hello とか foobar とかのファイル名で大丈夫ということです。仕様として意図しているかはわかりませんが、後述の例のように、1ファイルに複数の public classes があっても動きました！

> When the launcher reads the source file, if the file is not a Java source file (i.e. it is not a file whose name ends with .java) and if the first line begins with #!, then the contents of that line up to but not including the first newline are ignored when determining the source code to be passed to the compiler.

Shebang の行はコンパイラーには渡らず無視されます。Java の行コメントは `#` ではなく `//` ですが、この工夫によって動作するようになっています。

ファイル名が .java で終わっていると Java ソース ファイルとみなされてしまって、この箇所でコンパイル エラーが発生します。.java でさえなければ良いらしく、foobar.js や foobar.sh でも動作します。困ったことに .java では動かない仕様なので、どう頑張っても拡張子で使用言語を表現できません。「なんだこのスクリプト、バグってるぞ？」と思って他の人がファイルを開いたら、まさかの Java プログラム！ 絶対に驚かれると思います。

あとは普通の Java プログラムです。main メソッドの `String[] args` がいい仕事をしてくれます。



# 1. シェル コマンドを作ってみる

例題として、x 座標と y 座標を引数として与えると、第何象限なのかを返してくれるコマンドを作ってみました。

使う機会のほとんどない var で変数を宣言することによってスクリプト言語の味わいを出そうとしています。

```java:quadrant
#!/usr/bin/env java --source 11

public class Quadrant {
    public static void main(String[] args) {
        if (args.length == 2) {
            try {
                 var x = Double.parseDouble(args[0]);
                 var y = Double.parseDouble(args[1]);
                 String s;
                 switch(Coordinate2D.quadrantOf(x, y)) {
                     case 1:  s = "1st"; break;
                     case 2:  s = "2nd"; break;
                     case 3:  s = "3rd"; break;
                     case 4:  s = "4th"; break;
                     default: throw new IllegalStateException();
                 };
                 System.out.println("The quadrant of (" + x + ", " + y + ") is the " + s);
                 System.exit(0);
            } catch (Exception e) {
                 System.err.println(e.getMessage());
            }
        }

        // show usage and exit abnormally
        System.err.println("usage: quadrant x y");
        System.err.println("       where x and y are non-zero coordinate values in double");
        System.exit(1);
    }
}

public class Coordinate2D {
    public static int quadrantOf(double x, double y) {
        if (x > 0.0 && y > 0.0)
            return 1;
        else if (x < 0.0 && y > 0.0)
            return 2;
        else if (x < 0.0 && y < 0.0)
            return 3;
        else if (x > 0.0 && y < 0.0)
            return 4;
        else
            throw new IllegalArgumentException("point must not be on any axis");
    }
}
```

最近は IntelliJ を使うことが多かったのですが、今回はスクリプトを書く気分を味わうために vim を使いました。switch 文が詰め詰めの記述なのはフォーマッターを使っていないためです。switch 式のほうがバグが出にくいので好きですが、移植性の高いスクリプトにするために Java 11 とし、泣く泣く switch 文で書きました。

Java は書き慣れているからか、エラー処理を書きやすかった感じがします。シェル スクリプトではついついエラー処理がいい加減になってしまうのは私だけでしょうか？

```zsh
% chmod +x quadrant
% ./quadrant  
usage: quadrant x y
       where x and y are non-zero coordinate values in double
% echo $?
1
% ./quadrant 2 -3
The quadrant of (2.0, -3.0) is the 4th
% echo $?
0
```

動かしてみるとこんな感じです。これだけ見たら Java で書かれているとは思いもしませんよね。



# 2. Java スクリプトで JavaScript を実行してみる

次は、懐かしの Rhino を使って Java スクリプトで JavaScript を実行してみます。

![Rhino.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/782342/2ebd1482-51d7-0755-3aa9-e4f9ed0c214e.png)

https://github.com/mozilla/rhino の "JavaScript in Java" というメッセージは、今の気分にぴったりです。

実を言うと私は JDK に添付されていた頃には Rhino を使ったことがありません。今日始めて利用するのですが、公式ドキュメントがわかりづらい・・・。ですが、今回の目的は Java スクリプトで JavaScript を実行すること、ただそれだけです。

適当にググって回ったところ、[Rhinoを使ってJavaから外部JavaScriptファイルを呼び出す簡単なサンプル](https://teppei.hateblo.jp/entry/20100214/1266107748)というわかりやすいサンプルを見つけましたので、これをもっと簡単にして、JavaScript を実行する Java スクリプトにしました。

```java:javascript_in_java_script
#!/usr/bin/env java --source 11 -cp rhino-1.7.13.jar

import org.mozilla.javascript.Context;

class JavaScriptInJavaScript {
    public static void main(String[] args) throws Exception {
        var ctx = Context.enter();
        var obj = ctx.evaluateString(
            ctx.initStandardObjects(),
            "'Java' + 'Script'",
            "<Java script>",
            1,
            null
        );
        System.out.println(obj + " in a Java script!");
    }
}
```

Shebang でクラスパスに Rhino の jar を指定しています。

```zsh
% curl -OsSL https://github.com/mozilla/rhino/releases/download/Rhino1_7_13_Release/rhino-1.7.13.jar 
% chmod +x javascript_in_java_script 
% ./javascript_in_java_script 
JavaScript in a Java script!
```

Java スクリプトから `'Java' + 'Script'` という JavaScript を実行できました。



# おわりに

いかがでしたか？ ジャバスクリプトの概念を崩壊させるのは、やっていてちょっと楽しかったです。

真面目な顔をしてまとめます。

シェル スクリプトは各種 B シェルの細かい挙動の違いを把握できず、bash と特定の Linux ディストリビューションの組み合わせでしか動かないものになってしまいがちです。スクリプト言語を使えばこれを回避できますが、今度は perl や python3 が使えない環境にしばしば遭遇するという欠点があります。

一方で Java スクリプトを動かせる JDK の[^2] java コマンドは、Java 11 以降で開発している案件では高確率で利用できる、静的型付けである、Java プログラマーにとって移植性の確保が容易であるなどの特長がありますので、利用するスクリプト言語の候補として検討に値すると思います。

私にはまだちょっと実務で使う度胸はありませんが、読者の皆様におかれましては、"Write once, run anywhere[^3]" な Java スクリプト[^4]に挑戦してみるのも面白いのではないでしょうか。ご報告をお待ちしております。

あなたとJAVAスクリプト,
今すぐ実行

[^1]: `#!/usr/bin/env node` は「JavaScript スクリプト」です。異論は認めます。
[^2]: GraalVM は非対応です。
[^3]: Shebang を解釈する環境に限ります。
[^4]: お好みの拡張子をご利用ください。