反射与类加载
反射(Reflection)是 Java 的核心特性之一,它允许程序在运行时获取类的内部信息并操作对象的属性和方法。理解反射和类加载机制,是深入理解 Java 框架底层原理的基础。
一、反射概述
1.1 什么是反射
反射是在运行时获取类的结构信息(属性、方法、构造器等)并动态调用对象方法的机制。
// 普通方式:编译时确定类型
User user = new User();
user.setName("张三");
// 反射方式:运行时动态操作
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.getDeclaredConstructor().newInstance();
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(obj, "张三");1.2 反射的作用
| 作用 | 说明 |
|---|---|
| 动态加载类 | 运行时根据类名加载类 |
| 获取类信息 | 获取类名、属性、方法、构造器等 |
| 创建对象 | 动态创建任意类型的对象 |
| 调用方法 | 动态调用任意方法 |
| 访问属性 | 动态访问和修改属性(包括私有) |
| 框架基础 | Spring、MyBatis 等框架的核心技术 |
1.3 反射的缺点
| 缺点 | 说明 |
|---|---|
| 性能开销 | 反射比直接调用慢(JIT 优化有限) |
| 安全限制 | 可能绕过访问控制,破坏封装 |
| 代码可读性 | 反射代码难以理解和维护 |
| 编译检查失效 | 编译时无法检查类型错误 |
二、Class 类
2.1 获取 Class 对象
Java 中每种类型都有一个 Class 对象,共有四种获取方式:
// 方式1:类名.class(推荐)
Class<String> clazz1 = String.class;
Class<int[]> clazz2 = int[].class;
Class<Runnable> clazz3 = Runnable.class;
// 方式2:对象.getClass()
String str = "hello";
Class<? extends String> clazz4 = str.getClass();
// 方式3:Class.forName()(动态加载)
Class<?> clazz5 = Class.forName("java.lang.String");
// 方式4:类加载器(不常用)
Class<?> clazz6 = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
// 基本类型和包装类
Class<Integer> intClass = int.class; // 基本类型
Class<Integer> integerClass = Integer.class; // 包装类
Class<Integer> typeClass = Integer.TYPE; // 基本类型(Integer 内部常量)
System.out.println(intClass == typeClass); // true
System.out.println(intClass == integerClass); // false2.2 Class 类常用方法
Class<?> clazz = String.class;
// 类信息
clazz.getName(); // "java.lang.String"(全限定名)
clazz.getSimpleName(); // "String"(简单名称)
clazz.getPackage(); // "package java.lang"
clazz.getSuperclass(); // class java.lang.Object
clazz.getInterfaces(); // 实现的接口数组
clazz.getModifiers(); // 修饰符(public final = 17)
// 判断类型
clazz.isInterface(); // 是否接口
clazz.isArray(); // 是否数组
clazz.isEnum(); // 是否枚举
clazz.isPrimitive(); // 是否基本类型
clazz.isAnnotation(); // 是否注解
// 获取成员
clazz.getFields(); // 所有 public 属性(含继承)
clazz.getDeclaredFields(); // 所有声明的属性(不含继承)
clazz.getMethods(); // 所有 public 方法(含继承)
clazz.getDeclaredMethods(); // 所有声明的方法(不含继承)
clazz.getConstructors(); // 所有 public 构造器
clazz.getDeclaredConstructors();// 所有声明的构造器
// 创建实例
clazz.newInstance(); // 已废弃
clazz.getDeclaredConstructor().newInstance(); // 推荐三、反射操作
3.1 创建对象
// 方式1:无参构造
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.getDeclaredConstructor().newInstance();
// 方式2:有参构造
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
Object obj2 = constructor.newInstance("张三", 25);
// 方式3:私有构造(需要设置可访问)
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // 暴力反射
Object obj3 = privateConstructor.newInstance("李四");3.2 操作属性
public class User {
private String name;
public int age;
}
Class<?> clazz = User.class;
Object user = clazz.getDeclaredConstructor().newInstance();
// 获取 public 属性
Field ageField = clazz.getField("age");
ageField.set(user, 25);
System.out.println(ageField.get(user)); // 25
// 获取 private 属性
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 暴力反射:取消访问检查
nameField.set(user, "张三");
System.out.println(nameField.get(user)); // "张三"
// 静态属性
Field staticField = clazz.getDeclaredField("STATIC_FIELD");
staticField.setAccessible(true);
staticField.set(null, "value"); // 静态属性:对象传 null
System.out.println(staticField.get(null));3.3 调用方法
public class User {
public void sayHello() {
System.out.println("Hello!");
}
private String getName() {
return "张三";
}
public static void staticMethod() {
System.out.println("Static method");
}
}
Class<?> clazz = User.class;
Object user = clazz.getDeclaredConstructor().newInstance();
// 调用 public 方法
Method sayHello = clazz.getMethod("sayHello");
sayHello.invoke(user); // 输出: Hello!
// 调用 private 方法
Method getName = clazz.getDeclaredMethod("getName");
getName.setAccessible(true);
String name = (String) getName.invoke(user);
System.out.println(name); // 输出: 张三
// 调用静态方法
Method staticMethod = clazz.getMethod("staticMethod");
staticMethod.invoke(null); // 静态方法:对象传 null
// 有参方法
Method setName = clazz.getDeclaredMethod("setName", String.class);
setName.invoke(user, "李四");
// 有返回值方法
Method getName = clazz.getDeclaredMethod("getName");
String result = (String) getName.invoke(user);3.4 获取泛型信息
public class Demo {
private List<String> list;
private Map<String, Integer> map;
}
// 获取字段泛型类型
Field listField = Demo.class.getDeclaredField("list");
Type genericType = listField.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) genericType;
Type[] typeArgs = pt.getActualTypeArguments();
System.out.println(typeArgs[0]); // class java.lang.String
}
// 获取方法返回值泛型类型
Method method = SomeClass.class.getMethod("getList");
Type returnType = method.getGenericReturnType();3.5 获取注解信息
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface MyAnnotation {
String value();
int count() default 0;
}
@MyAnnotation(value = "class", count = 1)
public class Demo {
@MyAnnotation("field")
private String name;
@MyAnnotation("method")
public void test() {}
}
// 获取类注解
MyAnnotation classAnno = Demo.class.getAnnotation(MyAnnotation.class);
System.out.println(classAnno.value()); // "class"
// 获取属性注解
Field nameField = Demo.class.getDeclaredField("name");
MyAnnotation fieldAnno = nameField.getAnnotation(MyAnnotation.class);
System.out.println(fieldAnno.value()); // "field"
// 获取方法注解
Method testMethod = Demo.class.getMethod("test");
MyAnnotation methodAnno = testMethod.getAnnotation(MyAnnotation.class);
System.out.println(methodAnno.value()); // "method"四、类加载机制
4.1 类加载过程
类从被加载到 JVM 到卸载,经历以下阶段:
┌─────────────────────────────────────────────────────────┐
│ 类加载过程 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 加载 │ → │ 连接 │ → │ 初始化 │ → │ 使用 │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
│ │ │
│ ┌──────┼──────┐ │
│ │ │ │ │
│ ┌────┴─┐ ┌──┴──┐ ┌┴────┐ │
│ │验证 │ │准备 │ │解析 │ │
│ └──────┘ └─────┘ └─────┘ │
│ │
└─────────────────────────────────────────────────────────┘加载(Loading)
- 通过类全限定名获取类的二进制字节流
- 将字节流转化为方法区的运行时数据结构
- 在堆中生成一个
Class对象
连接(Linking)
| 阶段 | 说明 |
|---|---|
| 验证 | 确保 Class 文件格式正确,符合 JVM 规范 |
| 准备 | 为静态变量分配内存,设置默认初始值 |
| 解析 | 将符号引用转换为直接引用 |
public class Test {
public static int value = 123; // 准备阶段:value = 0
// 初始化阶段:value = 123
public static final int CONSTANT = 100; // 准备阶段:CONSTANT = 100(final)
}初始化(Initialization)
- 执行
<clinit>方法(静态变量赋值 + 静态代码块) - 按顺序收集类中所有静态变量的赋值动作和静态语句块
public class Test {
static {
System.out.println("静态代码块");
}
public static int a = 1;
// 编译后生成 <clinit> 方法:
// {
// System.out.println("静态代码块");
// a = 1;
// }
}4.2 类初始化时机
主动使用(会触发初始化):
- 创建类的实例(
new) - 访问类的静态变量(除
final常量) - 访问类的静态方法
- 反射调用(
Class.forName()) - 初始化子类(父类先初始化)
- JVM 启动时初始化主类
被动使用(不会触发初始化):
- 通过子类引用父类的静态变量
- 通过数组定义引用类
- 访问
static final常量
// 主动使用:会初始化
new User(); // new
User.name; // 访问静态变量
User.method(); // 访问静态方法
Class.forName("User"); // 反射
// 被动使用:不会初始化
User[] users = new User[10]; // 数组定义
System.out.println(User.CONSTANT); // final 常量
System.out.println(Child.parentStaticField); // 子类引用父类静态变量五、类加载器
5.1 类加载器层次
┌─────────────────────────────────────────────┐
│ Bootstrap ClassLoader │
│ (启动类加载器,C++ 实现) │
│ 加载核心类库 │
│ $JAVA_HOME/lib/rt.jar │
└──────────────────────┬──────────────────────┘
│
┌──────────────────────┴──────────────────────┐
│ Extension ClassLoader │
│ (扩展类加载器,Java 实现) │
│ 加载扩展类库 │
│ $JAVA_HOME/lib/ext/*.jar │
└──────────────────────┬──────────────────────┘
│
┌──────────────────────┴──────────────────────┐
│ Application ClassLoader │
│ (应用类加载器,Java 实现) │
│ 加载应用程序类 │
│ ClassPath 下的类 │
└─────────────────────────────────────────────┘5.2 类加载器代码示例
// 获取类加载器
ClassLoader loader = String.class.getClassLoader(); // null(Bootstrap)
ClassLoader loader2 = ClassLoader.getSystemClassLoader(); // AppClassLoader
// 类加载器层次
ClassLoader appLoader = Test.class.getClassLoader();
System.out.println(appLoader); // AppClassLoader
System.out.println(appLoader.getParent()); // PlatformClassLoader (JDK 9+)
System.out.println(appLoader.getParent().getParent()); // null (Bootstrap)
// JDK 8
// AppClassLoader → ExtClassLoader → null (Bootstrap)
// JDK 9+ (模块化)
// AppClassLoader → PlatformClassLoader → null (Bootstrap)5.3 自定义类加载器
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
private byte[] loadClassData(String name) throws IOException {
String fileName = classPath + File.separator + name.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(fileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
}
}
}
// 使用
MyClassLoader loader = new MyClassLoader("/path/to/classes");
Class<?> clazz = loader.loadClass("com.example.MyClass");六、双亲委派模型
6.1 什么是双亲委派
当一个类加载器收到类加载请求时,它首先把请求委托给父加载器处理,只有父加载器无法完成时,才尝试自己加载。
┌─────────────────────────────────────────────────────────┐
│ 双亲委派模型 │
├─────────────────────────────────────────────────────────┤
│ │
│ 加载请求 │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ AppClassLoader│ ──┐ │
│ └──────────────┘ │ 委托给父加载器 │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ExtClassLoader│ ←─┘ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │Bootstrap │ │
│ │ClassLoader │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ 找到了?返回;没找到?子加载器尝试 │
│ │
└─────────────────────────────────────────────────────────┘6.2 双亲委派的源码
// ClassLoader.loadClass() 方法
protected Class<?> loadClass(String name, boolean resolve) {
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 委托父加载器加载
c = parent.loadClass(name, false);
} else {
// 3. 委托 Bootstrap 加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载器无法加载,忽略
}
if (c == null) {
// 4. 父加载器都无法加载,自己尝试加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}6.3 双亲委派的好处
| 好处 | 说明 |
|---|---|
| 安全性 | 防止核心类库被篡改(如自定义 java.lang.String) |
| 唯一性 | 保证类的唯一性(全限定名 + 类加载器) |
| 一致性 | 核心类库由同一加载器加载,保证一致性 |
// 为什么不能自定义 java.lang.String?
// 即使定义了,由于双亲委派,Bootstrap 加载器会加载 rt.jar 中的 String
// 自定义的 String 不会被加载
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("自定义 String");
}
}
// 运行报错:找不到 main 方法(因为加载的是 rt.jar 中的 String)6.4 打破双亲委派
何时需要打破:
- SPI 机制(JDBC、JNDI 等)
- 热部署/热加载
- 模块化系统(OSGi、JDK 9 模块化)
// JDBC 打破双亲委派的例子
// DriverManager 在 rt.jar(Bootstrap 加载器加载)
// 但它需要加载应用程序的 JDBC Driver(AppClassLoader 加载)
// Bootstrap 无法向下委派,所以 DriverManager 使用 ContextClassLoader
// Thread Context ClassLoader
Thread.currentThread().setContextClassLoader(customLoader);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = loader.loadClass("com.example.Driver");七、反射性能优化
7.1 反射性能问题
// 直接调用 vs 反射调用性能对比
public class PerformanceTest {
public void test() {}
public static void main(String[] args) throws Exception {
long start, end;
// 直接调用(1亿次)
PerformanceTest obj = new PerformanceTest();
start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
obj.test();
}
end = System.currentTimeMillis();
System.out.println("直接调用: " + (end - start) + "ms"); // ~10ms
// 反射调用(1亿次)
Method method = PerformanceTest.class.getMethod("test");
start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
method.invoke(obj);
}
end = System.currentTimeMillis();
System.out.println("反射调用: " + (end - start) + "ms"); // ~500ms
}
}7.2 优化方法
1. 缓存反射对象
// 不缓存:每次都获取 Method
for (int i = 0; i < 1000; i++) {
Method method = clazz.getMethod("test"); // 每次都查找
method.invoke(obj);
}
// 缓存:只获取一次
Method method = clazz.getMethod("test"); // 只查找一次
for (int i = 0; i < 1000; i++) {
method.invoke(obj);
}2. 使用 MethodHandle(JDK 7+)
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.findVirtual(
User.class, "getName", MethodType.methodType(String.class)
);
String name = (String) handle.invoke(user);3. 使用字节码生成(ASM、CGLIB、ByteBuddy)
// Spring、MyBatis 等框架使用字节码技术生成代理类
// 避免反射性能问题7.3 setAccessible 的作用
// setAccessible(true) 的作用:
// 1. 绕过访问权限检查(访问私有成员)
// 2. 关闭安全检查,提升性能
Method method = clazz.getDeclaredMethod("privateMethod");
// 不设置:安全检查 + 访问检查
method.invoke(obj); // 可能抛出 IllegalAccessException
// 设置后:关闭安全检查
method.setAccessible(true);
method.invoke(obj); // 可以调用私有方法,性能也更好八、常见面试题
Q1: 什么是反射?有什么用?
A: 反射是在运行时获取类的结构信息并动态调用对象方法的机制。用途:
- 框架开发(Spring、MyBatis)
- 动态代理
- 注解处理
- 动态加载类
Q2: 获取 Class 对象的方式有哪些?
A:
类名.class对象.getClass()Class.forName("全限定名")ClassLoader.loadClass()
Q3: 类加载过程是什么?
A:
- 加载:读取 Class 文件,创建 Class 对象
- 连接:验证、准备(静态变量初始化默认值)、解析
- 初始化:执行
<clinit>方法(静态变量赋值 + 静态代码块)
Q4: 什么是双亲委派模型?有什么好处?
A: 双亲委派是指类加载器收到加载请求时,先委托父加载器处理,只有父加载器无法完成时才自己加载。
好处:
- 安全性:防止核心类库被篡改
- 唯一性:保证类的唯一性
- 一致性:核心类库由同一加载器加载
Q5: 什么情况下需要打破双亲委派?
A:
- SPI 机制(JDBC、JNDI)
- 热部署/热加载
- 模块化系统(OSGi、JDK 9 模块化)
Q6: 反射为什么慢?如何优化?
A: 慢的原因:
- 方法调用需要查找、验证、访问检查
- 无法 JIT 内联优化
- 需要创建额外的对象(参数数组等)
优化方法:
- 缓存反射对象(Method、Field 等)
- 使用
setAccessible(true)关闭安全检查 - 使用 MethodHandle
- 使用字节码生成技术
Q7: 静态代码块什么时候执行?
A: 类初始化时执行(<clinit> 方法),触发条件:
new创建实例- 访问静态变量(非 final)
- 访问静态方法
Class.forName()- 初始化子类
九、总结
| 概念 | 核心要点 | 面试关键词 |
|---|---|---|
| 反射 | 运行时获取类信息并操作 | Class、Method、Field |
| Class 对象 | 每种类型唯一,四种获取方式 | 类名.class、getClass、forName |
| 类加载 | 加载 → 连接 → 初始化 | <clinit>、静态初始化 |
| 类加载器 | Bootstrap → Extension → Application | 层次结构、委派 |
| 双亲委派 | 先委托父加载器,再自己加载 | 安全性、唯一性、打破委派 |
最后更新:2026年3月2日