Javaスクリプト入門

みんなでJavaスクリプトに入門しましょう!

クラスをちゃんと定義する

それでは、まずはJavaっぽく書いてみましょう。

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

これを、任意のファイル名で保存します。たとえばhello。
実行するにはJava 11以降が必要です。みなさんすでにSDKMANを入れてると思うのでOracle OpenJDKの12.0.1を使ってみましょう。

$ sdk use java 12.0.1-open

では実行します

$ java --source 12 hello
Hello!

実行できました!

これではちょっと面白くないので、先ほどのファイルの先頭にちょっとなにか追加しておきます。

#! /home/naoki/.sdkman/candidates/java/current/bin/java --source 12
public class Main {
  public static void main(String[] args) {
    System.out.println("Hello!");
  }
}

いわゆるShebangですね。

そして実行権限を付加

$ chmod +x hello

実行してみます。

$ ./hello
Hello!

やった!

これはJEP 330: Launch Single-File Source-Code Programsに基づくもので、Java 11から導入されています。
Java11ではjavacせずにJavaファイルが実行できるようになる - きしだのHatena

これを「スクリプト」と呼ばないとき、現代のスクリプト言語も実行時にネイティブコードになったりするので、どの時点でコンパイルされればスクリプトなのか(スクリプトではないのか)という話になっておもしろいですね。

もっとスクリプトっぽく書く

さっきのは あまりにもJavaだったので、もっとJavaっぽくなく書いてみます。

System.out.println("Hello2!")
/exit

このファイルを任意のファイル名で保存します。たとえばhello2。行末のセミコロンも不要です。

では実行します。実行するためにはJava 9以降が必要です。すでに先ほどJava 12を導入してますね。

$ jshell hello2
Hello2!

やった!

もうちょっと複雑なコードを書いてみましょう。

import java.time.*
var t = LocalDate.now()
System.out.println(t.minusMonths(1) + " to " + t)
System.out.println("Hello2!")
/exit

実行してみます。

$ jshell hello2
2019-05-29 to 2019-06-29
Hello2!

だいぶスクリプトっぽい!

注意が必要なのは、ブロックの中ではセミコロンが必要というところです。

import java.time.*
var t = LocalDate.now()
System.out.println(t.minusMonths(1) + " to " + t)
for (int i=0; i< 3; ++i) {
  System.out.println("Hello" + i);
}
/exit

実行してみます。

$ jshell hello2
2019-05-29 to 2019-06-29
Hello0
Hello1
Hello2

Shebangもやってみましょう。

#! /home/naoki/.sdkman/candidates/java/current/jshell
import java.time.*
var t = LocalDate.now()
System.out.println(t.minusMonths(1) + " to " + t)
for (int i=0; i< 3; ++i) {
  System.out.println("Hello" + i);
}
/exit

そして実行。

$ ./hello2
エラー:
'#'は不正な文字です
#! /home/naoki/.sdkman/candidates/java/current/bin/jshell
^
エラー:
式の開始が不正です
#! /home/naoki/.sdkman/candidates/java/current/bin/jshell
   ^
エラー:
式の開始が不正です
#! /home/naoki/.sdkman/candidates/java/current/bin/jshell
               ^
2019-05-29 to 2019-06-29
Hello0
Hello1
Hello2

がーん!!
実行されは するけど、#!の行もJavaとして解釈しようとするためにエラーになっちゃってますね。
これどうにかしないのかな?

7/1追記 BTSは立ってますね。 https://bugs.openjdk.java.net/browse/JDK-8167440

ちなみに、シェルスクリプトで1行目を無視するとかもありますが、OpenJDKソースを改変するなら、ここに

if (src.startsWith("#")) {
  return false;
}

を入れると#がコメントになってイケます。
https://github.com/openjdk/jdk/blob/master/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java#L1194

まとめ

Javaスクリプト最高!