Java SE 9 の予習 その4

Java SE 9 の予習 その4です。

前回のエントリーの続きでおそらく今回も必要とされない無駄なお遊びとなります。

今回は JShell API を利用してアプリケーション内でスニペットの評価や実行を試してみます。

なお、今回のエントリーには 「Java SE 9のProject Kulla、JShellの動作とJShell API (3/4)(JShell API)」の記事を参考にさせていただきました。

ほとんどプログラムは丸写し状態になっていますので元記事をまず読んで理解してこのお遊びプログラムを堪能していただければ幸いです。

下記プログラムは普通に19行目のコンストラクタ内で JShell.create() メソッドによりオブジェクトを生成します。

1

スニペットの評価実行は29行目の eval(String snipetSource) メソッドでおこなわれます。(折り畳んでいるので隠れてます。)

( 最後にいろいろゴニョゴニョ遊んだコードを載せますので行番号はずれてしまいますが参考にしてくださいませ。)

List<SnippetEvent> eval(String snipetSource) メソッドで SnippetEvent オブジェクトを取得します。

JavaDoc では 戻り値:この評価によって直接的または間接的に発生したイベントのリスト となっています。

つまり、ステータスや値です。

種類は、jdk.​jshell.​Snippet クラスの public Kind kind() メソッドで取得します。

戻り値である jdk.jshell.Snippet.Kind は jdk.​jshell.​Snippet のサブクラスを示します。

jdk.jshell.Snippet.Kind は jdk.​jshell.​Snippet クラスの public static enum Kind extends Enum<Kind> となっています。

つまり種類は jdk.​jshell.​Snippet クラスのイミュータブルプロパティです。

jdk.​jshell.​Snippetクラスには 種類を補足するための  public SubKind subKind() メソッドも用意されています。

これは jdk.​jshell.​Snippet クラスの  public static enum SubKind extends Enum<SubKind> を返します。

これはユーザーへのフィードバックに役立ちます。

これは種類の詳細な情報を取得するのに利用されます。

ステータスの取得には jdk.​jshell.​SnippetEvent クラスの public Status status() メソッドを使用します。

戻り値は jdk.​jshell.​Snippet クラスの public static enum Status extends Enum<Status> です。

これは JShell ステート内の動的プロパティです。

したがって JShell クエリで取得されます。

ステータスには次の7つの状態があります。

DROPPED:スニペットは、JShell.drop(Snippet)への明示的な呼び出しのために非アクティブです。

NONEXISTENT:スニペットはまだ存在しないため、非アクティブです。

OVERWRITTEN : 新しいスニペットに置き換えられているため、このスニペットは無効です。

RECOVERABLE_DEFINED:スニペットは、(現在のJShell状態のコンテキストで)回復可能な未解決の参照やその他の問題が本体内にある可能性のある宣言スニペットです。

RECOVERABLE_NOT_DEFINED:スニペットは、(現在のJShell状態のコンテキストで)回復可能な未解決の参照やその他の問題がある可能性のある宣言スニペットです。

REJECTED:スニペットは、初期評価時にコンパイルに失敗し、JShell状態のさらなる変更で有効になることができないため、非アクティブです。

VALID:スニペットは有効なスニペット(現在のJShell状態のコンテキスト内)です。

このプログラムではステータスが VALID でなければ jdk.​jshell.​JShell クラスの public Stream<Diag> diagnostics(Snippet snippet) メソッドでスニペットの最新評価の診断を取得します。

そして jdk.​jshell.​Diag クラスの public abstract String getMessage(Locale locale) メソッドで指定されたロケールのローカライズされたメッセージを返します。

実際のメッセージは実装依存です。 ロケールがnullの場合、デフォルトのロケールを使用します。

値の取得には、jdk.​jshell.​SnippetEvent クラスの public String value() メソッドを使います。

戻り値は成功した実行の結果値。

実行されなかった場合、または例外がスローされた場合、値はnullです。

以上、だいたいこんな感じで JShell API を楽しんでいきたいと思います。

動かすスニペットは例の「よく見かけるネタ IntegerCache 問題」です。

これだけだとさみしいので FunctionalInterface を利用した挨拶プログラムも JShell API で動かしてみましょう。

非常にシンプルなスニペットの実行となります。

Import statement、FunctionalInterface、などのステータス、値、種類などの情報を正確に取得できれば OK です。

では、このプログラムの実行結果を見てみましょう。

FunctionalInterface は、ただの Interface として識別されるようです。

IntegerCache 問題 もちゃんと false となっています。

3

これだと普通すぎて面白くないですよね。

日本ではもうすぐ Java SE 9 がリリースされようという時代に End Of Life となった Java SE 7 でプロフラムを組まなければいけない

涙無くしては語れない非常に過酷な環境に置かれているプログラマが多く存在するらしい。

そこでそういう可愛そうなプログラマのためにコンパイラオプションを JDK7 にし、

IntegerCache 問題で遊ぶために JVM オプションに -XX:AutoBoxCacheMax=128 を設定して実行してみます。

JShell オブジェクトの生成を JShell.create() メソッドから JShell.builder() メソッドに変更します。

4

5

コンパイラオプションの設定は

jdk.​jshell.​JShell.​Builder クラスの public Builder compilerOptions(String… options) メソッドと使い設定します。

可変長引数の使い方が独特で思いっきりドツボにはまったのは内緒です。

JVM オプションの設定は

jdk.​jshell.​JShell.​Builder クラスの public Builder remoteVMOptions(String… options) メソッドを使い設定します。

後は入出力の設定です。

これらは連鎖初期化で使用するため Builderインスタンスを返します。

最後に jdk.​jshell.​JShell.​Builder クラスの public JShell build() メソッドで JShell ステートエンジンを構築します。

これはすべてのJShell機能のエントリーポイントです。

これにより、実行のためのリモートプロセスが作成されます。

それではコンパイラオプションと JVM オプションが正しく機能するかプログラムを走らせてみましょう。

6

うわぁぁ!!

正しく期待通りの動作となったが何故か目から涙がこぼれそうに・・・

IntegerCache 問題の実行結果も true となり正しく JVM オプションが設定されたようです。

これだけで天の邪鬼な私のお遊びは終了です。

ここから先は思い込みでサプライズさせてもらったお話しです。

先のプログラムにコンパイラオプションと JVM オプションを設定しない元の状態に戻して下記スニペットの実行コードを追加してみました。

JShell では import 文を必要としないパッケージが何故か存在します。

java.util.Arrays もその一つだから無事にプログラムは実行されるだろうと思っていました。

7

((((;゜Д゜)))))))  なんと! ERROR! EXPRESSION [ シンボルを見つけられません・・・ ]

JShell API を利用する場合はおもてなしは無しなのか!?

8

しかたないから import 文を追記してみた。

9

今度は期待通りの動作で実行された。

10

ついでだから enum も試してみた。

11

ちゃんと enum 認識していた。

12

実は Linux 環境の NetBeans IDE だからちゃんと日本語対応されているのですが

Windows 環境だと日本語は文字化けしてしまいます。

13

セレブデベロッパーご愛用の IntelliJ IDEA (2017.2 EAP)でも Windows環境での JShellAPI で日本語は文字化けしてしまう。

これは Java SE 9 リリースまでに修正されるのだろうか?

14

ちなみに Windows のコマンドプロンプトからの実行は文字化けしません。

15

以上、JShell API を無駄に使って遊んでみたエントリーでした。

お終い!

おっと!JShell API と戯れた記念コードを載せておきますね。

package jp.yucchi.jshellapiexample;

import java.util.List;
import java.util.Locale;
import jdk.jshell.ErroneousSnippet;
import jdk.jshell.ExpressionSnippet;
import jdk.jshell.JShell;
import jdk.jshell.MethodSnippet;
import jdk.jshell.Snippet;
import jdk.jshell.Snippet.Status;
import jdk.jshell.SnippetEvent;
import jdk.jshell.TypeDeclSnippet;
import jdk.jshell.VarSnippet;

public class JShellAPIExample {

    private final JShell shell;

    public JShellAPIExample() {
	shell = JShell.create();
//        shell = JShell.builder()
//                .compilerOptions("-source", "1.7", "-target", "1.7",
//                        "-bootclasspath", "/home/yucchi/Sun/jdk1.7.0_80/jre/lib/rt.jar",
//                        "-extdirs", "")
//                .remoteVMOptions("-XX:AutoBoxCacheMax=128")
//                .in(System.in)
//                .err(System.err)
//                .out(System.out)
//                .build();
    }

    public void close() {
        if (shell != null) {
            shell.close();
        }
    }

    public void eval(String snippetSource) {

        System.out.println(snippetSource);

        List<SnippetEvent> se = shell.eval(snippetSource);

        se.forEach(e -> {
            if (e.causeSnippet() == null) {
                Snippet snippet = e.snippet();
                switch (snippet.kind()) {
                    case IMPORT:
                        switch (snippet.subKind()) {
                            case SINGLE_TYPE_IMPORT_SUBKIND:
                                System.out.println("<< Single-Type-Import Declaration. >>");
                                break;
                            case SINGLE_STATIC_IMPORT_SUBKIND:
                                System.out.println("<< Single-Static-Import Declaration. >>");
                                break;
                            case TYPE_IMPORT_ON_DEMAND_SUBKIND:
                                System.out.println("<< Type-Import-on-Demand Declaration. >>");
                                break;
                            case STATIC_IMPORT_ON_DEMAND_SUBKIND:
                                System.out.println("<< Static-Import-on-Demand Declaration. >>");
                                break;
                            default:
                                break;
                        }
                        break;

                    case ERRONEOUS:
                        System.out.println("ERROR! " + ((ErroneousSnippet) snippet).probableKind() + ":");
                        break;
                    case TYPE_DECL:
                        TypeDeclSnippet type = (TypeDeclSnippet) snippet;
                        System.out.print("<< Created ");

                        switch (snippet.subKind()) {
                            case CLASS_SUBKIND:
                                System.out.print("Class: ");
                                break;
                            case INTERFACE_SUBKIND:
                                System.out.print("Interface: ");
                                break;
                            case ENUM_SUBKIND:
                                System.out.print("Enum: ");
                                break;
                            case ANNOTATION_TYPE_SUBKIND:
                                System.out.print("Annotation: ");
                                break;
                            default:
                                break;
                        }
                        System.out.println(type.name() + " >>");
                        break;
                    case METHOD:
                        MethodSnippet method = (MethodSnippet) snippet;
                        System.out.println("<< Created Method: "
                                + method.name() + "("
                                + method.parameterTypes() + ")" + " >>");
                        break;
                    case VAR:
                        switch (snippet.subKind()) {
                            case VAR_DECLARATION_SUBKIND:
                                System.out.println("<< A variable declaration without initializer. >>");
                                System.out.println(((VarSnippet) snippet).name() + " ==> " + e.value());
                                break;
                            case TEMP_VAR_EXPRESSION_SUBKIND:
                                System.out.println("<< An expression whose value has been stored in a temporary variable. >>");
                                System.out.println(((VarSnippet) snippet).name() + " ==> " + e.value());
                                break;
                            case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND:
                                System.out.println("<< A variable declaration with an initializer expression. >>");
                                System.out.println(((VarSnippet) snippet).name() + " ==> " + e.value());
                                break;
                            default:
                                break;
                        }
                        break;
                    case EXPRESSION:
                        switch (snippet.subKind()) {
                            case VAR_VALUE_SUBKIND:
                                System.out.println("<< A simple variable reference expression. >>");
                                System.out.println(((ExpressionSnippet) snippet).name() + " ==> " + e.value());
                                break;
                            case ASSIGNMENT_SUBKIND:
                                System.out.println("<< An assignment expression. >>");
                                System.out.println(((ExpressionSnippet) snippet).name() + " ==> " + e.value());
                                break;
                            default:
                                break;
                        }
                        break;
                    case STATEMENT:
                        System.out.println("<< " + "Statement: " + e.snippet() + " >>");
                        break;
                    default:
                        System.out.println("<< " + e.getClass().getCanonicalName() + " >>");
                        break;
                }

                if (e.status() != Status.VALID) {
                    this.shell.diagnostics(e.snippet()).forEach(i -> {
                        System.out.println("[ " + i.getMessage(Locale.getDefault()) + " ]");
                    });
                }

            }
        });

    }

    public static void main(String[] args) throws Exception {

        JShellAPIExample example = new JShellAPIExample();

        example.eval("import java.time.LocalTime;");
        example.eval("@FunctionalInterface \n"
                + "interface Greeting<T, R> {\n"
                + "R apply(T time, R message);\n"
                + "\n"
                + "        default void sayGreeting(String greeting) {\n"
                + "            greeting(greeting);\n"
                + "        }\n"
                + "\n"
                + "        private void greeting(String greeting) {\n"
                + "            System.out.println(greeting + \" from JShellAPI.\");\n"
                + "        };\n"
                + "}");
        example.eval("LocalTime now = LocalTime.now();");
        example.eval("int hour = now.getHour();");
        example.eval("Greeting<Integer, String> greeting\n"
                + "                = (time, message) -> 4 <= time && time < 11 ? \"Good morning!\"\n"
                + "                        : 11 <= time && time < 18 ? \"Good afternoon!\"\n"
                + "                                : \"Good evening!\";");
        example.eval("String message = null;");
        example.eval("greeting.sayGreeting(greeting.apply(hour, message));");
        example.eval("");
        System.out.println("(○・ω・)ノ----------------------------------------------------------)\n");

        example.eval("Integer x = 128;");
        example.eval("Integer y = 128;");
        example.eval("void equivalenceTest(Integer x, Integer y) { System.out.println(x == y); }");
        example.eval("equivalenceTest(x, y);");
        System.out.println("(○・ω・)ノ----------------------------------------------------------)\n");

        example.eval("import java.util.Arrays;"); // 省略不可
        example.eval("String str;");
        example.eval("str = \"Java\";");
        example.eval("String[] elems = str.split(\"\");");
        example.eval("System.out.println(Arrays.toString(elems));");
        example.eval("Arrays.stream(elems).forEach(System.out::println);");
        System.out.println("(○・ω・)ノ----------------------------------------------------------)\n");

        example.eval("public enum JavaPlusYou {\n"
                + "\n"
                + "    Japanese(\"あなたとJAVA,今すぐダウンロード\"),\n"
                + "    English(\"JAVA+YOU, DOWNLOAD TODAY!\");\n"
                + "\n"
                + "    private final String downloadNow;\n"
                + "\n"
                + "    private JavaPlusYou(final String downloadNow) {\n"
                + "        this.downloadNow = downloadNow;\n"
                + "    }\n"
                + "\n"
                + "    public String getDownloadNow() {\n"
                + "        return downloadNow;\n"
                + "    }\n"
                + "\n"
                + "}");
        example.eval("for (JavaPlusYou jpy : JavaPlusYou.values()) {\n"
                + "            System.out.println(jpy + \": \" + jpy.getDownloadNow());\n"
                + "        }");
        System.out.println("(○・ω・)ノ----------------------------------------------------------)\n");

        example.eval("System.out.println(System.getProperty(\"java.vm.version\"));");
        example.eval("System.out.println(System.getProperty(\"java.class.version\"));");
        System.out.println("(○・ω・)ノ----------------------------------------------------------)\n");

        example.close();

    }

}

実行結果は次のようになります。

run:
import java.time.LocalTime;
<< Single-Type-Import Declaration. >>
@FunctionalInterface
interface Greeting<T, R> {
R apply(T time, R message);

        default void sayGreeting(String greeting) {
            greeting(greeting);
        }

        private void greeting(String greeting) {
            System.out.println(greeting + ” from JShellAPI.”);
        };
}
<< Created Interface: Greeting >>
LocalTime now = LocalTime.now();
<< A variable declaration with an initializer expression. >>
now ==> 17:28:24.474604
int hour = now.getHour();
<< A variable declaration with an initializer expression. >>
hour ==> 17
Greeting<Integer, String> greeting
                = (time, message) -> 4 <= time && time < 11 ? “Good morning!”
                        : 11 <= time && time < 18 ? “Good afternoon!”
                                : “Good evening!”;
<< A variable declaration with an initializer expression. >>
greeting ==> $Lambda$17/555826066@a67c67e
String message = null;
<< A variable declaration with an initializer expression. >>
message ==> null
greeting.sayGreeting(greeting.apply(hour, message));
Good afternoon! from JShellAPI.
<< Statement: Snippet:StatementKey#7-greeting.sayGreeting(greeting.apply(hour, message)); >>

(○・ω・)ノ———————————————————-)

Integer x = 128;
<< A variable declaration with an initializer expression. >>
x ==> 128
Integer y = 128;
<< A variable declaration with an initializer expression. >>
y ==> 128
void equivalenceTest(Integer x, Integer y) { System.out.println(x == y); }
<< Created Method: equivalenceTest(Integer,Integer) >>
equivalenceTest(x, y);
false
<< Statement: Snippet:StatementKey#11-equivalenceTest(x, y); >>
(○・ω・)ノ———————————————————-)

import java.util.Arrays;
<< Single-Type-Import Declaration. >>
String str;
<< A variable declaration without initializer. >>
str ==> null
str = “Java”;
<< An assignment expression. >>
str ==> “Java”
String[] elems = str.split(“”);
<< A variable declaration with an initializer expression. >>
elems ==> String[4] { “J”, “a”, “v”, “a” }
System.out.println(Arrays.toString(elems));
[J, a, v, a]
<< Statement: Snippet:StatementKey#16-System.out.println(Arrays.toString(elems)); >>
Arrays.stream(elems).forEach(System.out::println);
J
a
v
a
<< Statement: Snippet:StatementKey#17-Arrays.stream(elems).forEach(System.out::println); >>
(○・ω・)ノ———————————————————-)

public enum JavaPlusYou {

    Japanese(“あなたとJAVA,今すぐダウンロード”),
    English(“JAVA+YOU, DOWNLOAD TODAY!”);

    private final String downloadNow;

    private JavaPlusYou(final String downloadNow) {
        this.downloadNow = downloadNow;
    }

    public String getDownloadNow() {
        return downloadNow;
    }

}
<< Created Enum: JavaPlusYou >>
for (JavaPlusYou jpy : JavaPlusYou.values()) {
            System.out.println(jpy + “: ” + jpy.getDownloadNow());
        }
Japanese: あなたとJAVA,今すぐダウンロード
English: JAVA+YOU, DOWNLOAD TODAY!
<< Statement: Snippet:StatementKey#19-for (JavaPlusYou jpy : JavaPlusYou.values()) {
            System.out.println(jpy + “: ” + jpy.getDownloadNow());
        } >>
(○・ω・)ノ———————————————————-)

System.out.println(System.getProperty(“java.vm.version”));
9-ea+169
<< Statement: Snippet:StatementKey#20-System.out.println(System.getProperty(“java.vm.version”)); >>
System.out.println(System.getProperty(“java.class.version”));
53.0
<< Statement: Snippet:StatementKey#21-System.out.println(System.getProperty(“java.class.version”)); >>
(○・ω・)ノ———————————————————-)

ビルド成功(合計時間: 4秒)

Hatena タグ: ,

Java SE 9 の予習 その3

Java SE 9 の予習 その3です。

今回のエントリーは JShell です。

JShell は多くの開発者から期待されておりネット上に情報があふれています。

特に「最新Java情報局」の連載記事は解りやすくお勧めです。

そこで今回のエントリーは「こんな使い方はしないだろう」という内容のものを書きます。

つまり、ただのお遊びです。

でもネット上でこの情報を見ることができるのは此処だけかもしれませんよ。(^_^;

それだけ必要がない無駄な情報と言えます。

それでは過去のエントリーをまずご覧ください。

よく見かけるネタ IntegerCache 問題

Java ではよく知られている IntegerCache 問題です。

これは JVM オプションを設定することにより変更が可能です。

-XX:AutoBoxCacheMax=<size>

これを JShell で設定してうごかしてみたいと思いませんか?

思わない・・・ そう、普通は思いませんよね。

でも、私は天の邪鬼な人間なのでどうすればいいか必死になってググりました。

でもヒットせず twitter で泣きながらつぶやいたら「ジャバチョットデキル」優しい人達に救われました。

JShell は「Java SE 9のProject Kulla、JShellの動作とJShell API」の記事「JShellの動作」にあるように

JShell process と Java process のふたつの JVM が立ち上がっています。

つまり、JShell process からリモート JVM の Java process にコンパイルされたバイトコードを渡し実行した結果を返してもらっているようです。

だからリモート JVM に JVM オプションを設定しなければなりません。

まず、JShell を起動します。

a1

次に IntegerCache 問題のコードを入力していきます。

a2

ちゃんと false となっています。

これを true にするために下記の JVM オプションを JShell 起動コマンドに追加します。

-R-XX:AutoBoxCacheMax=128

a3

期待通りに true になってくれました。

次はプログラムから JShell API を使ってみようと思います。

今回のようにリモート JVM オプションを設定したり、コンパイルオプションを設定してみました。

またプログラムから JShell API を使うときに思い込みから「あれれ?」となったことも。

ちょっと今日は遅くなってしまったので次回にそれらは書く予定です。

それでは G’Night! Zzz…

Java SE 9 の予習

Java SE 9 の予習 その2

Hatena タグ: