広告

Java : JShell - API使用例

JShell (Java SE 18 & JDK 18) の使用例まとめです。
だいたいのメソッドを網羅済みです。
API仕様のおともにどうぞ。


概要

JShell評価状態エンジン。 これは、JShell APIの中心的なクラスです。 JShellインスタンスは、進化するコンパイルと実行状態を保持します。

クラス構成

JShellは、Javaのコードを対話的に実行(REPL)することができるコマンドラインツールです。

JShellはコマンドラインツールですが、Javaプログラムから直接使うこともできます。
そのためのクラスが、JShellクラスです。

try (final var js = JShell.create()) {

    js.eval("int x = 2;");
    js.eval("int y = 3;");
    js.eval("int z = x * y;");

    js.variables().forEach(varSnippet -> {
        System.out.printf("source [ %s ] : value [ %s ]%n",
                varSnippet.source(), js.varValue(varSnippet));
    });
}

// 結果
// ↓
//source [ int x = 2; ] : value [ 2 ]
//source [ int y = 3; ] : value [ 3 ]
//source [ int z = x * y; ] : value [ 6 ]

JShellそのものについては、下記の公式ドキュメントもご参照ください。


メソッド

void addToClasspath (String path)

指定されたパスは、eval()で使用されるクラスパスの末尾に追加されます。

// --- PowerShell ---
//PS R:\java-work> cat .\aaa\Sample.java
//package aaa;
//
//public class Sample {
//
//    public int sum(int x, int y) {
//        return x + y;
//    }
//}
//
//PS R:\java-work> javac .\aaa\Sample.java
//
//PS R:\java-work> ls -Name aaa
//Sample.class
//Sample.java

try (final var js = JShell.create()) {

    final var path = Path.of("R:", "java-work");
    System.out.println(path); // R:\java-work

    js.addToClasspath(path.toString());

    js.eval("import aaa.Sample;");
    js.eval("var sample = new Sample();");
    js.eval("var a = sample.sum(2, 3);");

    js.eval("System.out.println(a);"); // 5
}

static JShell.Builder builder ()

JShell.Builderのファクトリ・メソッドで、JShellのインスタンスを作成するために使用されます。

// JShell内のSystem.outの出力先を、builderで指定したPrintStreamへ設定します。
final var os = new ByteArrayOutputStream();
try (final var ps = new PrintStream(os)) {

    final var builder = JShell.builder();
    try (final var js = builder.out(ps).build()) {
        js.eval("""
                System.out.println("abcd");
                """);
    }
}

System.out.println(os); // "abcd"

void close ()

この状態エンジンを閉じます。

try-with-resources文 を使って自動でcloseすることをおすすめします。

try (final var js = JShell.create()) {
    js.eval("System.out.println(\"abcd\");"); // "abcd"
}

try-with-resources文を使わない例です。

final var js = JShell.create();
try {
    js.eval("System.out.println(\"abcd\");"); // "abcd"
} finally {
    js.close();
}

static JShell create ()

新しいJShell状態エンジンを作成します。

try (final var js = JShell.create()) {

    js.eval("int x = 2;");
    js.eval("int y = 3;");
    js.eval("int z = x * y;");

    js.variables().forEach(varSnippet -> {
        System.out.printf("source [ %s ] : value [ %s ]%n",
                varSnippet.source(), js.varValue(varSnippet));
    });
}

// 結果
// ↓
//source [ int x = 2; ] : value [ 2 ]
//source [ int y = 3; ] : value [ 3 ]
//source [ int z = x * y; ] : value [ 6 ]

Stream<Diag> diagnostics (Snippet snippet)

スニペットの最新評価の診断を返します。

try (final var js = JShell.create()) {
    // 意図的に、不十分なコードにしています。
    final var events = js.eval("int x = ");

    events.forEach(event -> {
        System.out.println(event.status()); // REJECTED

        js.diagnostics(event.snippet()).forEach(diag -> {

            System.out.println(diag.isError()); // true
            System.out.println(diag.getCode()); // compiler.err.premature.eof

            // "構文解析中にファイルの終わりに移りました"
            System.out.println(diag.getMessage(Locale.getDefault()));
        });
    });
}

List<SnippetEvent> drop (Snippet snippet)

ステートから宣言を削除します。

try (final var js = JShell.create()) {

    final var events1 = js.eval("int x = 1;");
    events1.forEach(event -> {
        System.out.println(event.status()); // VALID
    });

    final var events2 = js.eval("System.out.println(x);"); // 1
    events2.forEach(event -> {
        System.out.println(event.status()); // VALID
    });

    // 変数 x を削除します。
    events1.forEach(event -> {
        js.drop(event.snippet());
    });

    final var events3 = js.eval("System.out.println(x);");
    events3.forEach(event -> {
        System.out.println(event.status()); // REJECTED

        js.diagnostics(event.snippet()).forEach(diag -> {
            //シンボルを見つけられません
            //  シンボル:   変数 x
            //  場所: クラス
            System.out.println(diag.getMessage(Locale.getDefault()));
        });
    });
}

List<SnippetEvent> eval (String input)

該当する場合、定義および/または実行を含む入力文字列を評価します。

try (final var js = JShell.create()) {

    js.eval("int x = 2;");
    js.eval("int y = 3;");
    js.eval("int z = x * y;");

    js.variables().forEach(varSnippet -> {
        System.out.printf("source [ %s ] : value [ %s ]%n",
                varSnippet.source(), js.varValue(varSnippet));
    });
}

// 結果
// ↓
//source [ int x = 2; ] : value [ 2 ]
//source [ int y = 3; ] : value [ 3 ]
//source [ int z = x * y; ] : value [ 6 ]
try (final var js = JShell.create()) {

    // クラスも定義することができます。
    js.eval("""
            class A {
                String getMessage() {
                   return "abcd";
                }
            }
            """);

    js.eval("var a = new A();");
    js.eval("var message = a.getMessage();");

    js.eval("System.out.println(message);"); // "abcd"
}

Stream<ImportSnippet> imports ()

アクティブなインポート・スニペットを返します。

try (final var js = JShell.create()) {

    js.eval("import java.nio.file.Path;");
    js.eval("import java.time.LocalDate;");

    final var imports = js.imports();

    imports.forEach(importSnippet -> {
        System.out.println(importSnippet);
    });

    // 結果
    // ↓
    //Snippet:ImportKey(Path,SINGLE_TYPE_IMPORT_SUBKIND)#1-import java.nio.file.Path;
    //Snippet:ImportKey(LocalDate,SINGLE_TYPE_IMPORT_SUBKIND)#2-import java.time.LocalDate;
}

Stream<MethodSnippet> methods ()

アクティブなメソッド・スニペットを返します。

try (final var js = JShell.create()) {

    js.eval("int sum(int x, int y) { return x + y; }");

    js.eval("int a = sum(2, 3);");
    js.eval("System.out.println(a);"); // 5

    js.eval("int sub(int x, int y) { return x - y; }");

    js.eval("int b = sub(10, 2);");
    js.eval("System.out.println(b);"); // 8

    final var methods = js.methods();

    System.out.println("-- methods --");
    methods.forEach(methodSnippet -> {
        System.out.println(methodSnippet);
    });

    // 結果
    // ↓
    //-- methods --
    //MethodSnippet:sum/(int,int)int-int sum(int x, int y) { return x + y; }
    //MethodSnippet:sub/(int,int)int-int sub(int x, int y) { return x - y; }
}

JShell.Subscription onShutdown (Consumer<JShell> listener)

このJShellインスタンスが終了するときに呼び出されるコールバックを登録します。

final var listener = new Consumer<JShell>() {
    @Override
    public void accept(JShell jShell) {
        System.out.println("shutdown!");
    }
};

System.out.println("-- start --");

try (final var js = JShell.create()) {

    js.onShutdown(listener);

    js.eval("int a = 2 + 3;");
    js.eval("System.out.println(a);");
}

System.out.println("-- end --");

// 結果
// ↓
//-- start --
//5
//shutdown!
//-- end --

JShell.Subscription onSnippetEvent (Consumer<SnippetEvent> listener)

スニペットのステータスが変更されたときに呼び出されるコールバックを登録します。

final var listener = new Consumer<SnippetEvent>() {
    @Override
    public void accept(SnippetEvent event) {
        System.out.println("  -- onSnippetEvent start --");
        System.out.println("  event : " + event.status());

        final var snippet = event.snippet();
        System.out.println("  snippet kind : " + snippet.kind());
        System.out.println("  snippet source : " + snippet.source());

        System.out.println("  -- onSnippetEvent end --");
    }
};

System.out.println("-- start --");

try (final var js = JShell.create()) {

    js.onSnippetEvent(listener);

    js.eval("int a = 2 + 3;");
    js.eval("System.out.println(\"a = \" + a);");

}

System.out.println("-- end --");

// 結果
// ↓
//-- start --
//  -- onSnippetEvent start --
//  event : VALID
//  snippet kind : VAR
//  snippet source : int a = 2 + 3;
//  -- onSnippetEvent end --
//a = 5
//  -- onSnippetEvent start --
//  event : VALID
//  snippet kind : STATEMENT
//  snippet source : System.out.println("a = " + a);
//  -- onSnippetEvent end --
//-- end --

Stream<Snippet> snippets ()

すべてのスニペットを返します。

try (final var js = JShell.create()) {

    js.eval("int x = 2;");
    js.eval("int y = 3;");
    js.eval("int a = x + y;");
    js.eval("System.out.println(a);"); // 5

    final var snippets = js.snippets();

    System.out.println("-- snippets --");
    snippets.forEach(snippet -> {
        System.out.println(snippet);
    });

    // 結果
    // ↓
    //-- snippets --
    //Snippet:VariableKey(x)#1-int x = 2;
    //Snippet:VariableKey(y)#2-int y = 3;
    //Snippet:VariableKey(a)#3-int a = x + y;
    //Snippet:StatementKey#4-System.out.println(a);
}

SourceCodeAnalysis sourceCodeAnalysis ()

ソース・コード分析機能へのアクセス。

try (final var js = JShell.create()) {

    final var analysis = js.sourceCodeAnalysis();

    final var input = """
            int sum(int x, int y) { return x + y; }
            System.out.println(sum(2, 3));
            """;

    // 2つ以上のsnippetとなる文字列を、そのまま eval すると失敗します。

    js.eval(input).forEach(event -> {
        System.out.println(event.status()); // REJECTED
    });

    // 有効なsnippetとなる文字列と、それ以外を分解できます。
    final var completion = analysis.analyzeCompletion(input);

    final var source = completion.source();
    System.out.println(source); // int sum(int x, int y) { return x + y; }

    js.eval(source).forEach(event -> {
        System.out.println(event.status()); // VALID
    });

    final var remaining = completion.remaining();
    System.out.println(remaining); // System.out.println(sum(2, 3));

    js.eval(remaining); // 5
}

Snippet.Status status (Snippet snippet)

スニペットのステータスを返します。

try (final var js = JShell.create()) {

    js.eval("int x = 2;");
    js.eval("int y = 3;");

    // 意図的に、不十分なコードにしています。
    js.eval("int z = x * ");

    System.out.println("-- status --");
    js.snippets().forEach(snippet -> {
        final var status = js.status(snippet);
        System.out.println(status);
    });

    // 結果
    // ↓
    //-- status --
    //VALID
    //VALID
    //REJECTED
}

void stop ()

現在実行中の評価を停止しようとします。

final var scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

try (final var js = JShell.create()) {

    // 2秒後にstopします。
    scheduledExecutorService.schedule(() -> {
        System.out.println("-- JShell stop! --");
        js.stop();
    }, 2, TimeUnit.SECONDS);

    final var start = System.nanoTime();

    System.out.println("sleep start");
    js.eval("Thread.sleep(5000);");
    System.out.println("sleep end : " + (System.nanoTime() - start) / 1000000000.0 + " sec.");

} finally {
    scheduledExecutorService.shutdown();
}

final var ret = scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("term : " + ret);

// 結果
// ↓
//sleep start
//-- JShell stop! --
//sleep end : 2.0477107 sec.
//term : true

Stream<TypeDeclSnippet> types ()

アクティブな型宣言(クラス、インタフェース、注釈型、および列挙型) snippetsを返します。

try (final var js = JShell.create()) {

    js.eval("""
            class A {
                String getMessage() {
                   return "abcd";
                }
            }
            """);
    js.eval("var a = new A();");
    js.eval("System.out.println(a.getMessage());"); // "abcd"

    js.eval("""
            class B {
            }
            """);

    final var types = js.types();

    System.out.println("-- types --");
    types.forEach(type -> {
        System.out.println(type);
    });

    // 結果
    // ↓
    //-- types --
    //Snippet:ClassKey(A)#1-class A {
    //    String getMessage() {
    //       return "abcd";
    //    }
    //}
    //
    //Snippet:ClassKey(B)#4-class B {
    //}
}

Stream<String> unresolvedDependencies (DeclarationSnippet snippet)

RECOVERABLE_DEFINEDまたはRECOVERABLE_NOT_DEFINED宣言の場合、スニペットの現在の未解決の依存関係の名前。

try (final var js = JShell.create()) {

    // JShellでは、まだ定義されていない変数などが、メソッド定義で使用可能です。
    final var events = js.eval("int calc(int x) { return x * AAA; } ");

    events.forEach(event -> {
        System.out.println(event.status()); // RECOVERABLE_DEFINED

        if (event.snippet() instanceof DeclarationSnippet snippet) {

            final var stream = js.unresolvedDependencies(snippet);
            stream.forEach(s -> {
                // "variable AAA"
                System.out.println(s);
            });
        }
    });

    js.eval("var AAA = 3;");
    js.eval("System.out.println(calc(10));"); // 30
}

void unsubscribe (JShell.Subscription token)

コールバック・サブスクリプションを取消します。

// unsubscribeしない例です。
try (final var js = JShell.create()) {

    js.onShutdown(j -> {
        System.out.println("shutdown!");
    });

    js.eval("System.out.println(\"abcd\");"); // "abcd"
}

// 結果
// ↓
//abcd
//shutdown!
// unsubscribeする例です。
try (final var js = JShell.create()) {

    final var token = js.onShutdown(j -> {
        System.out.println("shutdown!");
    });

    js.eval("System.out.println(\"abcd\");"); // "abcd"

    // listenerの登録を解除します。
    js.unsubscribe(token);
}

// 結果
// ↓
//abcd

Stream<VarSnippet> variables ()

アクティブな変数スニペットを返します。

このメソッドの使用例は、eval(String input) にまとめて記載しました。
そちらのAPI使用例をご参照ください。

String varValue (VarSnippet snippet)

変数の現在の値を取得します。

このメソッドの使用例は、eval(String input) にまとめて記載しました。
そちらのAPI使用例をご参照ください。


ページの先頭へ