Class类不是日常编写的class,而是JVM为每个类型生成的唯一元数据对象;它由类加载器在加载字节码时内部创建,不可new,无公共构造器,代表类型本身的“身份证”。

Class类到底是不是“类”?先说结论
不是你日常写的那种 class Person,而是 JVM 在运行时为每个类型(类、接口、枚举、数组、甚至基本类型)自动生成的唯一元数据对象。它不能用 new 创建,没有公共构造器,只由类加载器在加载字节码时由 JVM 内部生成——换句话说,Person.class 得到的不是“Person 的实例”,而是“Person 这个类型本身的身份证”。
获取 Class 对象的三种方式,哪个该用?
实际开发中选哪一种,取决于你手头有什么、是否可控、以及是否要触发类初始化:
-
MyClass.class:编译期已知类型,最安全高效;不会触发类的静态块执行,适合做类型判断、泛型擦除后还原(如List不合法,但.class String.class合法) -
obj.getClass():有现成对象,且需要准确的**运行时类型**(比如子类实例传给父类参数,obj.getClass()返回的是子类 Class,而Parent.class永远是父类) -
Class.forName("com.example.MyClass"):字符串动态加载,会触发类的初始化(即执行 static 块),常用于配置驱动类(如Class.forName("com.mysql.cj.jdbc.Driver"))、插件机制;若类不存在或初始化失败,抛ClassNotFoundException或ExceptionInInitializerError
⚠️ 注意:ClassLoader.loadClass() 和 forName() 行为不同——前者默认不初始化类,后者默认会;这点在热部署、类隔离场景里极易踩坑。
Class 类能干啥?别只当“反射入口”用
它不只是 getDeclaredMethod() 的起点,更是运行时类型系统的基石:
立即学习“Java免费学习笔记(深入)”;
- 判断类型归属:
clazz.isInterface()、clazz.isArray()、clazz.isPrimitive(),比instanceof更底层、更灵活 - 跨层级类型检查:
clazz.getSuperclass()、clazz.getInterfaces(),做框架时解析继承关系必备 - 获取泛型信息:
clazz.getTypeParameters()、clazz.getGenericSuperclass(),配合ParameterizedType解析List中的User - 基础类型与包装类互通:
int.class和Integer.TYPE是同一个对象,但Integer.class != int.class;写通用序列化/类型转换工具时必须区分清楚
常见误用:拿 String[].class 去匹配 Object[].class ——它们是不同的 Class 实例,因为数组类型是协变的,但 Class 对象不共享。
为什么 Class 对象能“缓存”?双亲委派在这里起什么作用?
每个 Class 对象在 JVM 中全局唯一,靠的是类加载器 + 全限定名的双重约束。也就是说:AppClassLoader 加载的 java.util.ArrayList 和 CustomClassLoader 加载的同名类,是两个完全无关的 Class 对象,哪怕字节码一模一样。
双亲委派确保了核心类(如 java.lang.String)永远由启动类加载器加载,避免被恶意替换——这也是为什么你无法用自己的 String.class 覆盖系统 String 的原因。一旦某个 Class 对象被加载进方法区,后续所有对该类型的引用(无论通过哪种方式获取)都会返回同一个实例,所以 clazz1 == clazz2 在多数情况下成立,但前提是它们由同一个类加载器加载。
真正容易被忽略的是:Web 容器(如 Tomcat)和 OSGi 环境下,类加载器是分层的,getClass().getClassLoader() 的结果可能出乎意料——这时候光看类名没用,得比对 ClassLoader 实例本身。










