如何向JShell评估上下文添加本地变量?
向JShell上下文添加外部本地变量的可行方案
方案1:动态生成类+静态导入(稳定兼容)
这是最稳妥且适配多数JDK版本的方式,核心是把外部变量封装成静态类的字段,再让JShell静态导入该类:
动态生成包含外部变量的类
可以通过JavaCompiler动态编译源码生成类,以下是基于字符串源码的简单实现:// 主程序中的外部变量 String myText = "Hello from main"; User myUser = new User("Alice", 28); // 自定义引用类型 // 拼接动态类源码 String classCode = """ public class ExternalContext { public static String myText = "%s"; public static User myUser = %s; } """.formatted(myText, myUser); // 引用类型需确保同JVM可直接引用 // 编译类到临时目录(编译逻辑可通过ToolProvider.getSystemJavaCompiler()实现) Path tempDir = Files.createTempDirectory("jshell-ext"); // 将临时目录添加到JShell的类路径 jshell.addToClasspath(tempDir.toString()); // 在JShell中静态导入该类的所有字段 jshell.eval("import static ExternalContext.*;");完成后JShell即可直接使用
myText和myUser,且因使用DirectExecutionControl,引用类型变量会直接指向主程序中的原对象。动态添加导入的问题
JShell支持通过eval()直接执行导入语句,无需特殊API,这一步完全可行。
方案2:反射修改JShell内部变量容器(灵活但依赖JDK实现)
由于你使用DirectExecutionControl与主程序同JVM运行,可通过反射直接修改JShell自动生成的变量存储类:
JShell jshell = JShell.create(); // 先在JShell中定义占位变量 jshell.eval("String myText;"); jshell.eval("User myUser;"); // 获取对应变量的Snippet VarSnippet myTextSnippet = (VarSnippet) jshell.snippets().stream() .filter(s -> s.name().equals("myText")) .findFirst().orElseThrow(); VarSnippet myUserSnippet = (VarSnippet) jshell.snippets().stream() .filter(s -> s.name().equals("myUser")) .findFirst().orElseThrow(); // 反射获取DirectExecutionControl的内部类映射 Field stateField = JShell.class.getDeclaredField("state"); stateField.setAccessible(true); Object state = stateField.get(jshell); Field execControlField = state.getClass().getDeclaredField("executionControl"); execControlField.setAccessible(true); DirectExecutionControl execControl = (DirectExecutionControl) execControlField.get(state); Field classesField = DirectExecutionControl.class.getDeclaredField("classes"); classesField.setAccessible(true); Map<String, Class<?>> classes = (Map<String, Class<?>>) classesField.get(execControl); // 给JShell生成的变量类字段赋值 Class<?> textClass = classes.get(myTextSnippet.fullName()); Field textField = textClass.getDeclaredField("myText"); textField.setAccessible(true); textField.set(null, "Hello from reflection"); Class<?> userClass = classes.get(myUserSnippet.fullName()); Field userField = userClass.getDeclaredField("myUser"); userField.setAccessible(true); userField.set(null, new User("Bob", 30));
注意:这种方式依赖JShell内部实现细节,不同JDK版本(如JDK11与JDK17)的内部类结构可能变化,需针对性调整。
为什么官方默认不提供该功能?
JShell的设计初衷是隔离评估上下文与主程序上下文,避免JShell代码意外修改主程序变量,或主程序状态被JShell执行逻辑破坏,属于有意的设计隔离,因此未暴露直接添加外部变量的官方API。
对你思路的补充
- 动态类+静态导入:完全可行,动态导入直接通过
eval("import ...")实现即可; - 反射修改内部类:确实可以实现,但需要承担JDK版本兼容风险。
内容的提问来源于stack exchange,提问作者zdimension
相关产品推荐
相关产品推荐

