如何使用非公开类jdk.jshell.CompletenessAnalyzer实现自定义JShell自动补全?
基于JShell实现自定义Shell的命令自动补全方案
一、自定义命令自动补全实现
JShell原生Tab补全仅针对Java代码,要实现自定义命令的补全,需手动处理输入事件(Scanner无法捕获Tab键),推荐使用java.io.Console读取输入,结合自定义命令列表实现补全逻辑:
核心思路
- 用
Console替代Scanner,支持读取单个字符以捕获Tab键(ASCII码为9) - 维护自定义命令集合,匹配用户输入前缀生成补全候选
- 处理Tab输入时,输出匹配的补全选项或自动补全唯一匹配项
修改后的示例代码
import jdk.jshell.JShell; import java.io.Console; import java.util.ArrayList; import java.util.List; public class CustomShell { private static JShell jshell; private static final List<String> CUSTOM_COMMANDS = List.of("/exit", "/help", "/clear", "/list"); public static void main(String[] args) { jshell = JShell.create(); Console console = System.console(); if (console == null) { System.err.println("无法获取控制台,请在终端中运行程序"); System.exit(1); } System.out.println("Custom Shell"); System.out.println("Type '/exit' to exit the shell"); StringBuilder inputBuffer = new StringBuilder(); while (true) { console.printf("> "); int charCode; try { charCode = System.in.read(); } catch (Exception e) { break; } if (charCode == '\n' || charCode == '\r') { // 处理回车,执行命令 String userInput = inputBuffer.toString().trim(); inputBuffer.setLength(0); if (userInput.equals("/exit")) { break; } executeCommand(userInput); } else if (charCode == 9) { // Tab键触发补全 handleTabCompletion(inputBuffer, console); } else { // 普通字符追加到输入缓冲区并回显 inputBuffer.append((char) charCode); console.printf("%c", (char) charCode); } } jshell.close(); } private static void handleTabCompletion(StringBuilder inputBuffer, Console console) { String prefix = inputBuffer.toString().trim(); List<String> matches = new ArrayList<>(); for (String cmd : CUSTOM_COMMANDS) { if (cmd.startsWith(prefix)) { matches.add(cmd); } } if (matches.isEmpty()) { console.printf("\t"); return; } if (matches.size() == 1) { // 唯一匹配,自动补全剩余部分 String completion = matches.get(0).substring(prefix.length()); inputBuffer.append(completion); console.printf("%s", completion); } else { // 多个匹配,换行输出所有候选 console.printf("\n"); for (String match : matches) { console.printf("%s\n", match); } console.printf("> %s", inputBuffer); } } private static void executeCommand(String command) { jshell.eval(command).forEach(System.out::println); } }
二、非公开类jdk.jshell.CompletenessAnalyzer的使用
CompletenessAnalyzer是JShell内部用于分析代码完整性的非公开类,需通过反射调用,注意该类的API在不同JDK版本可能存在变动:
核心用法
- 通过反射获取类实例:依赖JShell内部的
JShell.State对象 - 调用
analyze方法判断输入代码是否为完整的Java语句/表达式
反射调用示例代码
import jdk.jshell.JShell; import java.lang.reflect.Method; import java.lang.reflect.Field; public class CompletenessAnalyzerDemo { public static void main(String[] args) throws Exception { JShell jshell = JShell.create(); // 获取JShell内部的state字段 Field stateField = JShell.class.getDeclaredField("state"); stateField.setAccessible(true); Object state = stateField.get(jshell); // 获取CompletenessAnalyzer类并创建实例 Class<?> analyzerClass = Class.forName("jdk.jshell.CompletenessAnalyzer"); Object analyzer = analyzerClass.getConstructor(state.getClass()).newInstance(state); // 获取analyze方法并调用 Method analyzeMethod = analyzerClass.getMethod("analyze", String.class); Object result = analyzeMethod.invoke(analyzer, "List<String> list = new Array"); // 解析结果枚举 Class<?> resultEnum = Class.forName("jdk.jshell.CompletenessAnalyzer$Result"); System.out.println("代码完整性状态:" + resultEnum.cast(result).name()); } }
注意事项
- 非公开类无官方兼容性保证,升级JDK时需验证逻辑是否可用
- 若仅需判断输入是否可执行,也可直接调用
JShell.eval捕获异常,但CompletenessAnalyzer能提前判断代码完整性,避免不必要的执行
内容的提问来源于stack exchange,提问作者mah454
相关产品推荐
相关产品推荐

