如何指定用JDK而非JRE运行Jar?JavaCompiler编译空指针问题
你遇到的这个问题确实很典型——根源就是JRE和JDK的核心差异:NetBeans默认是用JDK来启动你的应用的,JDK里完整包含了Java编译器相关的工具类,所以JavaCompiler实例能正常初始化;但直接双击Jar或者用系统默认的java -jar命令运行时,系统大概率调用的是JRE环境,JRE是JDK的精简版,去掉了编译工具链,这就导致javaCompiler对象本身是null,调用getTask()方法自然就抛出空指针异常了。
下面给你几个实用的解决方案,按需选择:
方案一:指定用JDK的java命令运行Jar包
不要直接双击Jar,打开命令行终端,找到你本地JDK安装目录下的bin/java.exe(Windows)或者bin/java(Linux/Mac),用它来执行你的Jar包,示例命令如下:# 替换成你自己的JDK路径和Jar文件名 C:\Program Files\Java\jdk1.8.0_301\bin\java -jar your-application.jar方案二:修改系统默认的Jar打开方式(Windows专属)
如果你习惯双击运行Jar,可以右键点击Jar文件,选择「打开方式」→「选择其他应用」→「更多应用」→「在这台电脑上查找其他应用」,定位到JDK目录下的java.exe,勾选「始终使用此应用打开.jar文件」,以后双击Jar就会自动用JDK启动了。方案三:打包时引入tools.jar(仅适用于JDK 8及以下版本)
对于JDK 8及更早的版本,编译工具类都放在JDK_HOME/lib/tools.jar里,你可以把这个Jar包添加到项目依赖中,打包时一起打进应用Jar里。注意:JDK 9及以上版本已经移除了tools.jar,模块化系统将编译工具整合到了jdk.compiler模块,这个方案不适用。方案四:动态适配JDK版本加载编译器(兼容JDK 9+)
如果你需要兼容更高版本的JDK,可以通过模块系统动态获取编译器实例,避免空指针,示例代码如下:JavaCompiler javaCompiler = null; try { // 先尝试通过ToolProvider获取 javaCompiler = ToolProvider.getSystemJavaCompiler(); if (javaCompiler == null) { // JDK 9+ 尝试通过模块加载编译器 ModuleLayer bootLayer = ModuleLayer.boot(); ClassLoader compilerClassLoader = bootLayer.findLoader("jdk.compiler").orElse(null); if (compilerClassLoader != null) { Class<?> javacToolClass = compilerClassLoader.loadClass("com.sun.tools.javac.api.JavacTool"); javaCompiler = (JavaCompiler) javacToolClass.getDeclaredConstructor().newInstance(); } } } catch (Exception e) { e.printStackTrace(); } if (javaCompiler == null) { throw new IllegalStateException("无法找到Java编译器,请使用JDK环境运行此应用"); } // 后续正常调用getTask()方法 final CompilationTask task = javaCompiler.getTask(null, null, diagnostics, null, null, compilationUnits);
内容的提问来源于stack exchange,提问作者Zee Best

