这三类错误都是“运行时和编译时看到的 Java 类不一样”,但修法完全不同。这篇用 JDK 21 在本机造两个版本的库,让应用对着 v1 编译、运行时换成 v2,逐个复现:缺类、static 初始化失败、方法/字段/接口变化的各种后果,以及两个 jar 里有同名类时怎么确定到底加载了谁。
编译期有、运行期 classpath 有 Lib(退出码 0):
Lib v1
运行期 classpath 去掉 Lib(直接引用 Lib.hi())(退出码 1):
Exception in thread "main" java.lang.NoClassDefFoundError: Lib
Caused by: java.lang.ClassNotFoundException: Lib
... 1 more
两种写法同时试(退出码 0):
捕获 ClassNotFoundException: Lib
捕获 NoClassDefFoundError: Lib | cause = java.lang.ClassNotFoundException: LibClassNotFoundException 是按名字找失败,NoClassDefFoundError 是链接时少了,后者的 cause 是前者。
static 初始化抛异常(退出码 0):
第 1 次调用 Lib.hi(): ExceptionInInitializerError: null | cause = java.lang.IllegalStateException: 读取配置失败:db.url 未设置
第 2 次调用 Lib.hi(): NoClassDefFoundError: Could not initialize class Lib | cause = java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: 读取配置失败:db.url 未设置 [in thread "main"]
第 3 次调用 Lib.hi(): NoClassDefFoundError: Could not initialize class Lib | cause = java.lang.ExceptionInInitializerError: Exception java.lang.IllegalStateException: 读取配置失败:db.url 未设置 [in thread "main"]第一次是 ExceptionInInitializerError,之后是 Could not initialize class,要去找最早的那条日志。
删除了方法 add(int,int):java.lang.NoSuchMethodError: 'int Lib.add(int, int)'
方法 ping() 的返回类型 void→int(源码调用不用改):java.lang.NoSuchMethodError: 'void Lib.ping()'
方法 add(int,int)→add(long,long):java.lang.NoSuchMethodError: 'int Lib.add(int, int)'
删除了字段 sfield:java.lang.NoSuchFieldError: Class Lib does not have member field 'int sfield'
static 方法 util() 改成实例方法:java.lang.IncompatibleClassChangeError: Expected static method 'void Lib.util()'
方法 shout() 改成 private:java.lang.IllegalAccessError: class Main tried to access private method 'void Lib.shout()' (Main and Lib are in unnamed module of loader 'app')
接口新增了抽象方法 b(),Impl 是对着旧接口(v1)编译的,调用方对着新接口(v2)编译:
a() 调用成功
java.lang.AbstractMethodError: Receiver class Impl does not define or inherit an implementation of the resolved method 'abstract void b()' of interface Svc.
重新对着 v2 编译应用:
<tmp>/app/Impl.java:1: error: Impl is not abstract and does not override abstract method b() in Svc方法、字段、static 修饰、可见性和接口的改动各对应不同的 Error,报错里的签名是应用编译时看到的那个。
classpath 顺序 v1.jar:v2.jar -> Lib 来自 v1 | 加载自 file:<tmp>/lib_v1.jar classpath 顺序 v2.jar:v1.jar -> Lib 来自 v2 | 加载自 file:<tmp>/lib_v2.jar -Xlog:class+load 里 Lib 的那一行:file:<tmp>/lib_v1.jar jar tf 能看到同名类:v1.jar 含 Lib.class ; v2.jar 含 Lib.class
classpath 先出现的赢,用 getCodeSource 或 -Xlog:class+load 确认来源。