Class对象是JVM为每个类加载后创建的唯一类型元数据容器,承载结构信息并作为反射入口;获取方式有String.class、obj.getClass()、Class.forName()三种,内存地址相同但场景与安全性不同。

Class对象本质是运行时类型元数据容器
Java中每个类在JVM加载后都会生成一个唯一的Class对象,它不是你new出来的实例,而是JVM为该类型自动创建的“类型身份证”。它不描述某个具体对象的状态,而是承载这个类的完整结构信息:字段、方法、构造器、父类、接口、注解、泛型签名等。
常见误解是把它当成“类的模板”或“工厂”,其实它更接近反射系统的入口点——所有Field、Method、Constructor对象都由它派生而来。
获取Class对象的三种方式及区别
不同方式获取的Class对象指向同一个内存地址(同一类型只有一份),但适用场景和安全性不同:
-
String.class:编译期确定,最安全,推荐用于已知类名的场景;不能用于基本类型包装(int.class合法,但Integer.class≠int.class) -
obj.getClass():运行时动态获取,适用于多态处理;注意null会抛NullPointerException -
Class.forName("java.util.ArrayList"):通过字符串加载类,触发类初始化(执行static块),可能抛ClassNotFoundException或ExceptionInInitializerError
示例对比:
立即学习“Java免费学习笔记(深入)”;
System.out.println(String.class == "hello".getClass()); // true
System.out.println(int.class == Integer.TYPE); // true(注意:Integer.TYPE 是 int.class 的别名)
System.out.println(Class.forName("java.lang.String") == String.class); // true
Class对象常被误用的反射操作
拿到Class对象后,很多开发者直接调用getDeclaredMethods()或newInstance(),却忽略权限与兼容性问题:
-
getDeclaredXXX()系列方法返回本类声明的所有成员(含private),但默认不可访问;必须显式调用setAccessible(true)才能绕过访问控制——这在Java 12+模块系统下可能被拒绝(如对jdk.internal.*类) -
newInstance()已在Java 9标记为deprecated,因无法处理带参数构造器且不支持SecurityManager检查;应改用getDeclaredConstructor().newInstance(...) - 泛型擦除导致
clazz.getTypeParameters()只能拿到形参名(如T),无法还原实际类型;需配合Method.getGenericReturnType()等接口解析
Class对象与类型安全、性能的关键交界点
Class本身不参与编译期类型检查,但它是instanceof、强制转型、ClassLoader.loadClass()等机制的底层支撑。几个易被忽略的事实:
- 数组类型也有
Class对象:int[].class、String[][].class,它们与元素类型的Class无继承关系(int[].class.getSuperclass() == Object.class) - 原始类型(
int、boolean)和其包装类(Integer、Boolean)的Class对象不同,但Integer.TYPE == int.class - 频繁调用
Class.forName()可能成为性能瓶颈(涉及类加载、字节码验证),应缓存结果;而.class字面量是零开销的编译期常量
真正复杂的地方在于:当你混合使用泛型、代理、模块化、自定义类加载器时,Class对象的getClassLoader()是否一致,直接决定isAssignableFrom()和类型转换能否成功——这点在线上排查ClassCastException时最容易被跳过。










