Java反射机制允许程序在运行期获取类的结构信息并动态调用其成员。Class类是这一切的起点,而java.lang.reflect包中的类库则提供了操作方法的工具。掌握二者如何配合,是理解Spring、MyBatis等框架底层原理的基础。

一、Class类:反射的元数据入口
在Java中,每个加载到内存的类都有一个对应的Class对象,它由JVM在类加载时创建,保存了类的名称、父类、接口、构造器、方法和字段等信息。我们拿到Class对象后,才能进一步通过反射API探查和操作这个类。
获取Class对象主要有三种方式。第一种是使用类字面量,例如String.class,这种方式在编译期就确定类型且不会触发类的初始化;第二种是通过对象的getClass方法,适用于已经有实例的场景;第三种是使用Class.forName,它根据全限定名加载类并返回Class对象,很多框架读取配置文件时都采用这种方式。
// 方式一:类字面量
Class<String> c1 = String.class;
// 方式二:对象实例
String s = "";
Class<? extends String> c2 = s.getClass();
// 方式三:动态加载
try {
Class<?> c3 = Class.forName("java.util.ArrayList");
System.out.println(c3.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
二、java.lang.reflect包中的常用反射类库
java.lang.reflect包是Java标准库提供的反射工具集,核心类包括Field、Method、Constructor、Modifier和Array等。它们分别代表类的字段、方法、构造器、修饰符信息和数组操作能力。这些类通常由Class对象的方法返回,自身不负责查找,只负责承载结构与提供调用能力。
例如,Class.getDeclaredMethods返回Method数组,包含本类声明的所有方法;Class.getFields则返回public字段。Method类提供了invoke来执行方法,Field类提供了get和set来读写属性。下面代码展示如何通过Class与Method配合调用一个私有方法。
import java.lang.reflect.Method;
public class Demo {
private void hello(String name) {
System.out.println("hello " + name);
}
public static void main(String[] args) throws Exception {
Class<Demo> clazz = Demo.class;
// 获取声明的方法,包括private
Method m = clazz.getDeclaredMethod("hello", String.class);
// 私有方法必须设置可访问
m.setAccessible(true);
Demo obj = clazz.getDeclaredConstructor().newInstance();
m.invoke(obj, "world");
}
}
三、Class与反射类库的协作流程
典型协作流程是:先通过Class定位类型,再用Class提供的方法拿到reflect包中的对象,最后由这些对象执行具体操作。这种分工让Class专注于类型元数据检索,reflect类专注于行为调用,职责清晰。
以动态创建对象并赋值字段为例,先用Class.forName拿到类型,再用getDeclaredConstructor新建实例,接着用getDeclaredField取得Field对象并set值。整个过程没有在代码中写死具体类名,全部由字符串驱动,这就是许多IOC容器的工作缩影。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
class User {
private String name;
public String toString() { return "User:" + name; }
}
public class Test {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("User");
Constructor<?> con = clazz.getDeclaredConstructor();
Object u = con.newInstance();
Field f = clazz.getDeclaredField("name");
f.setAccessible(true);
f.set(u, "tom");
System.out.println(u);
}
}
四、使用注意与性能考量
反射虽灵活,但每次调用都会进行安全检查并解析符号引用,比直接调用慢很多。在高频路径上应缓存Method、Field对象,避免反复查找。同时,setAccessible(true)会绕过私有保护,仅应在受控环境下使用,否则破坏封装。
另外,Class本身不提供调用能力,若误以为直接拿Class就能执行方法就会写出错误代码。明确Class负责“找”,reflect类负责“做”,才能写出健壮的反射逻辑。在模块化Java中,还需在module-info中开放包给反射,否则会抛出InaccessibleObjectException。
| 对比项 | Class类 | reflect类库 |
|---|---|---|
| 主要职责 | 类型元数据检索 | 成员操作与调用 |
| 典型方法 | forName, getDeclaredMethod | invoke, set, get |
| 获取方式 | 类加载产生 | 由Class方法返回 |
五、小结
Class是反射体系的枢纽,reflect类库是延伸的工具手。理解二者边界与配合方式,能够帮助我们在不修改编译期代码的前提下实现高度灵活的运行期行为,也为阅读主流框架源码打下基础。
在实际项目中,建议将反射调用封装在工具类中,统一处理异常与缓存,减少对业务代码的侵入。这样既能享受动态能力,又能控制复杂度和性能风险。