因为JVM判定类“相同”需同时满足全限定名和ClassLoader实例一致,不同加载器生成的Class对象内存隔离、静态变量独立、初始化互不触发,故==、equals()、instanceof均返回false。

不同 ClassLoader 加载的同名类,在 JVM 中是完全独立的两个类型,既不相等,也不兼容。
为什么它们不相等
Java 判定两个 Class 对象是否“相同”,依据的是“全限定类名 + 加载它的 ClassLoader 实例”这一组合。哪怕字节码一模一样、来自同一 JAR、包名类名完全相同,只要加载器不同,JVM 就视为两个无关类型。
- == 比较返回 false:不是同一个 Class 对象引用
- equals() 返回 false:Class.equals() 内部也检查 classloader
- instanceof 失败:A 加载的 User 不能被 B 加载的 User 类型接收
- 强制转型抛 ClassCastException:类型系统层面拒绝互通
隔离带来的实际表现
这种不相等不是表面现象,而是深入运行时结构的物理隔离:
- 静态变量各自独立:模块 A 的
Config.VERSION和模块 B 的同名字段互不影响 - 方法区元数据分离:常量池、字段表、方法表等各存一份,内存地址无交集
- 类初始化互不触发:A 中首次访问触发 A 版本的
<clinit>,B 版本不受影响 - 反射行为隔离:
A_Class.getDeclaredFields()只返回 A 加载版本的字段信息
常见误判场景
容易以为“类名一样就能通用”,但实际中几个典型陷阱要注意:
- 把 A 加载的
UserContext实例传给 B 模块的方法——参数类型不匹配,直接报错 - 用 AppClassLoader 加载过某工具类后,再用子加载器去加载同名类——双亲委派会命中父类已加载结果,导致隔离失效
- 多个模块共用一个 URLClassLoader 实例——看似“不同加载器”,实则仍是同一命名空间
- 未显式切断双亲委派(如没重写
loadClass()),关键业务类仍被父加载器统一接管
验证方式很简单
运行时可直接检查关键属性:
-
clazz.getClassLoader():确认归属加载器实例 -
clazz.getName():核对全限定名是否一致 -
System.identityHashCode(clazz):观察底层对象地址差异 - 尝试跨加载器赋值或转型,看是否抛出
ClassCastException


















