知识模块
☕ Java 知识模块
一、Java 基础
反射与类加载

反射与类加载

反射(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);  // false

2.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 类初始化时机

主动使用(会触发初始化)

  1. 创建类的实例(new
  2. 访问类的静态变量(除 final 常量)
  3. 访问类的静态方法
  4. 反射调用(Class.forName()
  5. 初始化子类(父类先初始化)
  6. JVM 启动时初始化主类

被动使用(不会触发初始化)

  1. 通过子类引用父类的静态变量
  2. 通过数组定义引用类
  3. 访问 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:

  1. 类名.class
  2. 对象.getClass()
  3. Class.forName("全限定名")
  4. ClassLoader.loadClass()

Q3: 类加载过程是什么?

A:

  1. 加载:读取 Class 文件,创建 Class 对象
  2. 连接:验证、准备(静态变量初始化默认值)、解析
  3. 初始化:执行 <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日