Java : JShell - API使用例

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


概要

クラス構成

Java Shellツール(JShell)は、Javaプログラミング言語を学習したり、およびJavaコードのプロトタイプを作成するための対話型ツールです。JDK 9で導入されました。JShellはRead-Evaluate-Print Loop (REPL)であり、入力時に宣言、文および式を評価し、ただちに結果を表示します。ツールは、コマンドラインから実行されます。

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

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

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

    final var path = Path.of("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 builder = JShell.builder();

final var os = new ByteArrayOutputStream();
try (final var ps = new PrintStream(os)) {
    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 -> {

        //source [ int x = 2; ] : value [ 2 ]
        //source [ int y = 3; ] : value [ 3 ]
        //source [ int z = x * y; ] : value [ 6 ]
        System.out.println("source [ %s ] : value [ %s ]".formatted(
                varSnippet.source(), js.varValue(varSnippet)));
    });
}

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 -> {

        //source [ int x = 2; ] : value [ 2 ]
        //source [ int y = 3; ] : value [ 3 ]
        //source [ int z = x * y; ] : value [ 6 ]
        System.out.println("source [ %s ] : value [ %s ]".formatted(
                varSnippet.source(), js.varValue(varSnippet)));
    });
}
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.time.LocalDate;");
    js.eval("System.out.println(LocalDate.now());"); // "2021-07-26"

    final var imports = js.imports();

    imports.forEach(importSnippet -> {
        // Snippet:ImportKey(LocalDate,SINGLE_TYPE_IMPORT_SUBKIND)#1-import java.time.LocalDate;
        System.out.println(importSnippet);
    });
}

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

    final var methods = js.methods();

    methods.forEach(methodSnippet -> {
        // MethodSnippet:sum/(int,int)int-int sum(int x, int y) { return x + y; }
        System.out.println(methodSnippet);
    });
}

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);");

}

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

// 結果
// ↓
//-- start --
//  -- onSnippetEvent start --
//  event : VALID
//  snippet kind : VAR
//  snippet source : int a = 2 + 3;
//  -- onSnippetEvent end --
//5
//  -- onSnippetEvent start --
//  event : VALID
//  snippet kind : STATEMENT
//  snippet source : System.out.println(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();

    snippets.forEach(snippet -> {
        //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);
        System.out.println(snippet);
    });
}

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));
            """;

    // 1つ以上の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 * ");

    js.snippets().forEach(snippet -> {
        final var status = js.status(snippet);

        //VALID
        //VALID
        //REJECTED
        System.out.println(status);
    });
}

void stop ()

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

final var scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

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

    // 7秒後にstopします。
    scheduledExecutorService.schedule(() -> js.stop(), 7, TimeUnit.SECONDS);

    final var start = System.nanoTime();

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

} finally {
    scheduledExecutorService.shutdown();
}

// ↓結果
//
//sleep start
//sleep end : 7.0293592 sec.

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"

    final var types = js.types();

    types.forEach(type -> {

        //Snippet:ClassKey(A)#1-class A {
        //    String getMessage() {
        //       return "abcd";
        //    }
        //}
        System.out.println(type);
    });
}

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の使用例にvariablesもまとめて記載しました。
API使用例はそちらをご参照ください。

String varValue (VarSnippet snippet)

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

evalの使用例にvarValueもまとめて記載しました。
API使用例はそちらをご参照ください。


ページの先頭へ