编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
      • 流程语句
      • 函数方法
      • 类和对象
      • 继承和多态
      • 接口和抽象类
      • 异常处理
      • 集合框架
      • IO流和File
      • 线程和锁
      • 泛型
      • 注解和反射
        • 15.1 注解入门
          • 15.1.1 注解是什么
          • 15.1.2 内置注解
          • 15.1.3 元注解
          • 15.1.4 综合案例:内置注解使用指南
          • 15.1.5 注解入门训练题
        • 15.2 自定义注解
          • 15.2.1 自定义注解语法
          • 15.2.2 自定义注解案例
          • 15.2.3 注解的底层原理
          • 15.2.4 自定义注解训练题
        • 15.3 反射机制
          • 15.3.1 反射概念
          • 15.3.2 获取Class对象
          • 15.3.3 获取类信息
          • 15.3.4 创建对象
          • 15.3.5 调用方法
          • 15.3.6 访问字段
          • 15.3.7 综合案例:对象检查器
          • 15.3.8 反射机制训练题
        • 15.4 注解和反射结合
          • 15.4.1 综合案例:简易字段校验器
          • 15.4.2 注解反射结合训练题
        • 15.5 反射的优缺点
          • 15.5.1 综合案例:反射性能测试
          • 15.5.2 反射优缺点训练题
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

注解和反射

# 15.注解和反射

# 目录介绍

  • 15.1 注解入门
    • 15.1.1 注解是什么
    • 15.1.2 内置注解
    • 15.1.3 元注解
    • 15.1.4 综合案例:内置注解使用指南
    • 15.1.5 注解入门训练题
  • 15.2 自定义注解
    • 15.2.1 自定义注解语法
    • 15.2.2 自定义注解案例
    • 15.2.3 注解的底层原理
    • 15.2.4 自定义注解训练题
  • 15.3 反射机制
    • 15.3.1 反射概念
    • 15.3.2 获取Class对象
    • 15.3.3 获取类信息
    • 15.3.4 创建对象
    • 15.3.5 调用方法
    • 15.3.6 访问字段
    • 15.3.7 综合案例:对象检查器
    • 15.3.8 反射机制训练题
  • 15.4 注解和反射结合
    • 15.4.1 综合案例:简易字段校验器
    • 15.4.2 注解反射结合训练题
  • 15.5 反射的优缺点
    • 15.5.1 综合案例:反射性能测试
    • 15.5.2 反射优缺点训练题

# 15.1 注解入门

# 15.1.1 注解是什么

注解(Annotation)是 Java 5 引入的一种元数据机制,用于为代码提供额外的信息。注解不会直接影响程序逻辑,但可以被编译器、框架或运行时工具读取和处理。

对比 C++:C++ 没有内置的注解机制(C++11 引入了 [[deprecated]] 等属性),Java 的注解系统更加完善和强大。注解是 Spring、MyBatis 等框架的核心基础。

注解的发展历程:

版本 新增内容
JDK 5 引入注解机制:@Override、@Deprecated、@SuppressWarnings
JDK 6 引入注解处理器 API(APT,javax.annotation.processing)
JDK 7 支持 @SafeVarargs
JDK 8 类型注解(@Target(ElementType.TYPE_USE))、重复注解(@Repeatable)
JDK 9 @Deprecated 增加 forRemoval 和 since 属性
JDK 14 @Serial 注解(序列化相关)

JDK 8 重复注解的使用:

// JDK 8 之前,同一个位置不能重复使用同一个注解
// JDK 8+ 通过 @Repeatable 支持重复注解

@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedule {
    String dayOfWeek();
    String time();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Schedules {
    Schedule[] value();
}

// 使用重复注解
@Schedule(dayOfWeek = "Monday", time = "09:00")
@Schedule(dayOfWeek = "Wednesday", time = "14:00")
@Schedule(dayOfWeek = "Friday", time = "09:00")
public void weeklyMeeting() {
    // 每周三次会议
}

// 通过反射读取重复注解
Method method = MyClass.class.getMethod("weeklyMeeting");
Schedule[] schedules = method.getAnnotationsByType(Schedule.class);
for (Schedule s : schedules) {
    System.out.println(s.dayOfWeek() + " " + s.time());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 15.1.2 内置注解

// @Override:标记方法重写了父类方法
@Override
public String toString() {
    return "Account";
}

// @Deprecated:标记已过时的方法
@Deprecated
public void oldMethod() {
    // 不推荐使用
}

// @SuppressWarnings:抑制编译器警告
@SuppressWarnings("unchecked")
public void method() {
    List list = new ArrayList();  // 不会产生警告
}

// @FunctionalInterface:标记函数式接口(JDK 8+)
@FunctionalInterface
public interface MyFunction {
    void execute();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 15.1.3 元注解

元注解是用于注解"注解"的注解:

元注解 作用
@Target 注解可以用在哪里(类、方法、字段等)
@Retention 注解的生命周期(源码、编译、运行时)
@Documented 注解会包含在 javadoc 中
@Inherited 注解可以被子类继承
@Target(ElementType.METHOD)              // 只能用在方法上
@Retention(RetentionPolicy.RUNTIME)      // 运行时保留
public @interface MyAnnotation {
    String value();
}
1
2
3
4
5

@Retention 的三种策略详解:

策略 生命周期 典型应用
SOURCE 只存在于源码中,编译时丢弃 @Override、@SuppressWarnings(编译器检查后不再需要)
CLASS 存在于 .class 文件中,但运行时不可见(默认值) 某些字节码分析工具使用
RUNTIME 运行时可通过反射获取 Spring @Autowired、JUnit @Test 等框架注解

疑惑:为什么 @Override 只需要 SOURCE 级别而不需要 RUNTIME?

答疑:@Override 的唯一作用是让编译器检查"这个方法确实重写了父类方法"。一旦编译通过,这个检查就完成了,运行时不需要再读取这个注解。如果设为 RUNTIME,反而会增加 .class 文件大小和运行时开销。

论证:而 Spring 的 @Autowired 必须是 RUNTIME,因为 Spring 容器在运行时需要扫描类的字段,找到带 @Autowired 注解的字段,然后通过反射注入依赖。如果是 SOURCE 或 CLASS 级别,Spring 在运行时就读不到这个注解了。

结果展示:所以选择 @Retention 策略的原则很简单——如果注解只在编译期有用,用 SOURCE;如果运行时需要读取,用 RUNTIME。绝大多数自定义框架注解都应该选择 RUNTIME。

# 15.1.4 综合案例:内置注解使用指南

本案例综合运用 Java 内置注解和元注解,演示注解的常见使用场景。

import java.lang.annotation.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 内置注解使用指南 —— 注解入门综合案例
 * 知识点:@Override、@Deprecated、@SuppressWarnings、@FunctionalInterface
 */

@FunctionalInterface  // 标记函数式接口
interface Greeting {
    String greet(String name);
}

class Animal {
    public void speak() {
        System.out.println("...");
    }

    @Deprecated(since = "2.0", forRemoval = true)  // 标记过时
    public void oldMethod() {
        System.out.println("旧方法,请使用 newMethod()");
    }

    public void newMethod() {
        System.out.println("新方法");
    }
}

class Dog extends Animal {
    @Override  // 编译器检查是否正确重写
    public void speak() {
        System.out.println("汪汪!");
    }
}

public class AnnotationGuide {
    @SuppressWarnings("unchecked")  // 抑制未检查警告
    public static void main(String[] args) {
        System.out.println("===== 内置注解使用指南 =====\n");

        // 1. @Override
        System.out.println("--- @Override ---");
        Animal dog = new Dog();
        dog.speak();
        System.out.println("  确保方法正确重写,拼写错误编译器会报错");

        // 2. @Deprecated
        System.out.println("\n--- @Deprecated ---");
        dog.oldMethod();  // IDE 会显示删除线
        dog.newMethod();
        System.out.println("  标记过时的API,编译时产生警告");

        // 3. @SuppressWarnings
        System.out.println("\n--- @SuppressWarnings ---");
        List rawList = new ArrayList();  // 无泛型会有警告
        rawList.add("hello");
        System.out.println("  抑制了 unchecked 警告");

        // 4. @FunctionalInterface
        System.out.println("\n--- @FunctionalInterface ---");
        Greeting g = name -> "你好, " + name + "!";
        System.out.println("  " + g.greet("Java"));
        System.out.println("  确保接口只有一个抽象方法,可用 Lambda");

        // 5. 总结
        System.out.println("\n--- 注解分类 ---");
        System.out.println("  标准注解 : @Override, @Deprecated, @SuppressWarnings");
        System.out.println("  元注解   : @Target, @Retention, @Documented, @Inherited");
        System.out.println("  自定义注解: @interface MyAnnotation {}");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

# 15.1.5 注解入门训练题

训练1:写一个方法,故意不加 @Override 注解重写 toString() 方法,然后把方法名拼错为 tostring()。观察编译器是否报错。再加上 @Override 观察区别。

训练2:使用 @Deprecated 标记一个方法,然后在另一个方法中调用它。观察 IDE 和编译器的提示信息。

思考:Java 的注解本质上是一种特殊的接口(所有注解隐式继承 java.lang.annotation.Annotation)。这意味着什么?能否用 implements 来实现一个注解接口?

# 15.2 自定义注解

# 15.2.1 自定义注解语法

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiInfo {
    String author() default "unknown";
    String version() default "1.0";
    String description();
}
1
2
3
4
5
6
7
8
9

# 15.2.2 自定义注解案例

@ApiInfo(author = "yangchong", version = "2.0", description = "银行账户类")
public class Account {
    private String name;
    private double balance;

    @ApiInfo(description = "存款方法")
    public void deposit(double amount) {
        balance += amount;
    }
}
1
2
3
4
5
6
7
8
9
10

# 15.2.3 注解的底层原理

注解在字节码中的表现:注解信息存储在 .class 文件的属性表中。对于 RUNTIME 注解,存储在 RuntimeVisibleAnnotations 属性中;对于 CLASS 注解,存储在 RuntimeInvisibleAnnotations 属性中。

注解的本质:每个注解在编译后会生成一个继承 java.lang.annotation.Annotation 的接口。当通过反射获取注解实例时(如 getAnnotation(ApiInfo.class)),JVM 会使用动态代理为这个接口创建一个代理对象,代理对象内部维护一个 Map,存储注解属性的名称和值。

// 反编译 @ApiInfo 的本质
public interface ApiInfo extends java.lang.annotation.Annotation {
    String author();    // 这些是接口方法
    String version();
    String description();
}

// 当调用 cls.getAnnotation(ApiInfo.class) 时:
// JVM 返回一个 Proxy 对象,实现了 ApiInfo 接口
// 调用 info.author() 实际上是从内部 Map 中取值
1
2
3
4
5
6
7
8
9
10

# 15.2.4 自定义注解训练题

训练1:创建一个 @NotNull 注解(@Target(FIELD), @Retention(RUNTIME)),并编写一个验证方法,通过反射检查对象中所有标注了 @NotNull 的字段是否为 null。

训练2:创建一个 @Log 注解(@Target(METHOD), @Retention(RUNTIME)),包含 level 属性(默认 "INFO")。然后编写工具方法,扫描一个类中所有带 @Log 注解的方法并打印其名称和日志级别。

思考:注解属性只能是以下类型:基本类型、String、Class、枚举、注解、以上类型的数组。为什么不能是任意对象(比如 List、Map)?

# 15.3 反射机制

# 15.3.1 反射概念

反射(Reflection)是 Java 的一种机制,允许程序在运行时获取类的信息(类名、方法、字段等),并动态地创建对象、调用方法、访问字段。

反射的底层原理:每个类被 ClassLoader 加载后,JVM 会在方法区(元空间)创建一个对应的 java.lang.Class 对象,其中包含了该类的完整结构信息(从 .class 文件解析而来)。反射 API 就是通过这个 Class 对象来获取字段、方法、构造器等元数据的。反射调用方法时,前 15 次使用JNI(Java Native Interface) 实现的 NativeMethodAccessorImpl,之后会动态生成字节码切换为 GeneratedMethodAccessorXxx(称为膨胀 Inflation),以提升后续调用性能。反射调用比直接调用慢的原因主要有:无法内联优化、需要检查访问权限、参数装箱拆箱、方法查找等额外开销。

反射是很多框架的基础(Spring 的依赖注入、MyBatis 的 ORM 映射等)。

疑惑:反射看起来就是"在运行时获取类信息",为什么说它很重要?

在编译时我们就知道一个类有什么字段和方法,为什么还需要在运行时去"反射"获取?直接调用不就行了吗?

答疑:反射的价值在于解耦。当你编写框架代码时,你不知道用户会写什么类。比如 Spring 的 @Autowired 需要自动注入依赖,但 Spring 框架在编写时不可能知道用户的 UserService 类有哪些字段。唯一的办法就是在运行时通过反射扫描类信息。

论证:考虑以下对比:

// 没有反射:硬编码,每加一个类就要改代码
public Object createBean(String className) {
    if (className.equals("UserService")) return new UserService();
    if (className.equals("OrderService")) return new OrderService();
    // 每新增一个类都要加 if-else...
}

// 使用反射:完全通用
public Object createBean(String className) throws Exception {
    Class<?> cls = Class.forName(className);
    return cls.getDeclaredConstructor().newInstance();
    // 无论什么类,同一套代码都能处理
}
1
2
3
4
5
6
7
8
9
10
11
12
13

结果展示:反射让框架代码与业务代码完全解耦。Spring、Hibernate、Jackson、JUnit 等框架的核心都建立在反射之上。没有反射,就没有现代 Java 框架生态。当然,反射的代价是性能和类型安全的损失,因此在业务代码中不建议滥用。

Class 对象的加载时机:

// Class 对象在类第一次被使用时由 ClassLoader 加载
// 以下操作会触发类加载:
// 1. new 关键字
// 2. 访问静态成员
// 3. Class.forName()
// 4. 反射操作
// 5. 子类加载时会先加载父类

// 类加载过程:加载 → 链接(验证→准备→解析)→ 初始化
// 加载:读取 .class 文件,创建 Class 对象
// 验证:校验字节码格式的正确性
// 准备:为静态变量分配内存,赋默认值
// 解析:将符号引用转为直接引用
// 初始化:执行 <clinit>()(静态变量赋值和静态代码块)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 15.3.2 获取Class对象

// 方式1:类名.class
Class<Account> cls1 = Account.class;

// 方式2:对象.getClass()
Account acc = new Account();
Class<?> cls2 = acc.getClass();

// 方式3:Class.forName(最灵活,类名可以是字符串变量)
Class<?> cls3 = Class.forName("com.bank.model.Account");

System.out.println(cls1 == cls2);  // true(同一个Class对象)
1
2
3
4
5
6
7
8
9
10
11

三种获取 Class 对象方式的区别:

方式 是否触发类初始化 使用场景
类名.class 不会触发 编译时已知类名
对象.getClass() 类已初始化 运行时有对象实例
Class.forName() 会触发初始化 运行时类名是字符串变量(最灵活)
// Class.forName 会触发类的初始化(执行静态代码块)
// 这就是 JDBC 加载驱动的原理:
Class.forName("com.mysql.cj.jdbc.Driver");
// MySQL Driver 类有一个静态代码块:
// static { DriverManager.registerDriver(new Driver()); }
// Class.forName 触发了这个静态代码块的执行

// 如果不想触发初始化:
Class<?> cls = Class.forName("com.example.MyClass", false, classLoader);
// 第二个参数 false 表示不初始化
1
2
3
4
5
6
7
8
9
10

基本类型和数组的 Class 对象:

// 基本类型也有 Class 对象
Class<Integer> intClass = int.class;     // 注意:int.class != Integer.class
Class<Void> voidClass = void.class;      // void 也有 Class 对象

// 数组也有 Class 对象
Class<?> arrClass = int[].class;
System.out.println(arrClass.getName());  // [I
Class<?> arrClass2 = String[].class;
System.out.println(arrClass2.getName()); // [Ljava.lang.String;

// 判断是否为数组
System.out.println(arrClass.isArray());  // true
1
2
3
4
5
6
7
8
9
10
11
12

# 15.3.3 获取类信息

Class<Account> cls = Account.class;

// 获取类名
System.out.println(cls.getName());           // com.bank.model.Account
System.out.println(cls.getSimpleName());     // Account

// 获取所有公共方法(包括继承的)
Method[] methods = cls.getMethods();
for (Method m : methods) {
    System.out.println(m.getName());
}

// 获取所有声明的字段(包括私有的)
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
    System.out.println(f.getName() + " : " + f.getType().getSimpleName());
}

// 获取构造方法
Constructor<?>[] constructors = cls.getConstructors();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

getMethods() vs getDeclaredMethods() 的区别:

方法 范围 包含私有 包含继承的
getMethods() public 方法 否 是
getDeclaredMethods() 所有方法 是 否
getFields() public 字段 否 是
getDeclaredFields() 所有字段 是 否

规律:带 Declared 的方法可以获取私有成员,但不包含继承的;不带 Declared 的只能获取 public 成员,但包含继承的。

# 15.3.4 创建对象

Class<Account> cls = Account.class;

// 通过无参构造创建
Account acc1 = cls.getDeclaredConstructor().newInstance();

// 通过有参构造创建
Constructor<Account> constructor = cls.getDeclaredConstructor(String.class, double.class);
Account acc2 = constructor.newInstance("张三", 1000.0);
1
2
3
4
5
6
7
8

# 15.3.5 调用方法

Class<Account> cls = Account.class;
Account acc = new Account("张三", 1000);

// 获取方法并调用
Method depositMethod = cls.getMethod("deposit", double.class);
depositMethod.invoke(acc, 500.0);  // 等价于 acc.deposit(500.0)

// 调用私有方法
Method privateMethod = cls.getDeclaredMethod("validateAmount", double.class);
privateMethod.setAccessible(true);  // 允许访问私有方法
Object result = privateMethod.invoke(acc, 100.0);
1
2
3
4
5
6
7
8
9
10
11

invoke 方法的参数传递:

// 无参方法
Method getName = cls.getMethod("getName");
String name = (String) getName.invoke(acc);

// 有多个参数的方法
Method transfer = cls.getMethod("transfer", Account.class, double.class);
transfer.invoke(acc, targetAcc, 500.0);

// 静态方法(第一个参数传 null)
Method valueOf = Integer.class.getMethod("valueOf", String.class);
Integer num = (Integer) valueOf.invoke(null, "123");

// 可变参数方法(需要特殊处理)
Method format = String.class.getMethod("format", String.class, Object[].class);
String result2 = (String) format.invoke(null, "Hello %s, age %d", new Object[]{"Alice", 25});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 15.3.6 访问字段

Class<Account> cls = Account.class;
Account acc = new Account("张三", 1000);

// 访问私有字段
Field balanceField = cls.getDeclaredField("balance");
balanceField.setAccessible(true);  // 允许访问私有字段

double balance = (double) balanceField.get(acc);  // 读取
System.out.println("余额:" + balance);

balanceField.set(acc, 9999.0);  // 修改
System.out.println("修改后余额:" + acc.getBalance());
1
2
3
4
5
6
7
8
9
10
11
12

# 15.3.7 综合案例:对象检查器

本案例综合运用反射机制的 Class 对象、构造器、方法和字段操作。

import java.lang.reflect.*;

/**
 * 对象检查器 —— 反射机制综合案例
 * 知识点:获取Class、遍历字段/方法、创建对象、调用方法、访问私有成员
 */
class Person {
    private String name;
    private int age;

    public Person() { this("未知", 0); }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private String getInfo() {
        return name + "(" + age + "岁)";
    }

    public void greet() {
        System.out.println("    你好,我是" + name);
    }
}

public class ObjectInspector {

    static void inspect(Class<?> clazz) {
        System.out.println("  类名: " + clazz.getName());

        System.out.println("  构造器:");
        for (Constructor<?> c : clazz.getDeclaredConstructors()) {
            System.out.println("    " + c);
        }

        System.out.println("  字段:");
        for (Field f : clazz.getDeclaredFields()) {
            System.out.printf("    %s %s %s%n",
                    Modifier.toString(f.getModifiers()), f.getType().getSimpleName(), f.getName());
        }

        System.out.println("  方法:");
        for (Method m : clazz.getDeclaredMethods()) {
            System.out.printf("    %s %s %s()%n",
                    Modifier.toString(m.getModifiers()), m.getReturnType().getSimpleName(), m.getName());
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("===== 对象检查器 =====\n");

        // 1. 获取 Class 对象的三种方式
        System.out.println("--- 获取 Class ---");
        Class<?> c1 = Person.class;
        Class<?> c2 = new Person().getClass();
        Class<?> c3 = Class.forName("Person");
        System.out.println("  三种方式同一个: " + (c1 == c2 && c2 == c3));

        // 2. 检查类结构
        System.out.println("\n--- 类结构检查 ---");
        inspect(Person.class);

        // 3. 反射创建对象
        System.out.println("\n--- 反射创建对象 ---");
        Constructor<?> ctor = Person.class.getDeclaredConstructor(String.class, int.class);
        Person p = (Person) ctor.newInstance("张三", 25);
        p.greet();

        // 4. 反射访问私有字段
        System.out.println("\n--- 访问私有字段 ---");
        Field nameField = Person.class.getDeclaredField("name");
        nameField.setAccessible(true);  // 突破 private
        System.out.println("  name = " + nameField.get(p));
        nameField.set(p, "李四");
        System.out.println("  修改后 name = " + nameField.get(p));

        // 5. 反射调用私有方法
        System.out.println("\n--- 调用私有方法 ---");
        Method getInfo = Person.class.getDeclaredMethod("getInfo");
        getInfo.setAccessible(true);
        System.out.println("  getInfo() = " + getInfo.invoke(p));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 15.3.8 反射机制训练题

训练1:编写一个通用的 toString 方法:String reflectToString(Object obj),通过反射读取对象所有字段的名称和值,拼接成 "ClassName{field1=value1, field2=value2}" 的格式。

训练2:编写一个方法 Object deepCopy(Object obj),通过反射实现对象的深拷贝(创建新对象,逐字段复制值)。只需支持基本类型和 String 类型的字段。

训练3:通过反射获取 java.util.ArrayList 类的所有构造方法,打印它们的参数类型。然后用反射创建一个初始容量为 100 的 ArrayList。

思考:反射可以调用私有构造方法(setAccessible(true)),这意味着单例模式可能被破坏。如何防止反射破坏单例?(提示:枚举单例。)

# 15.4 注解和反射结合

反射可以在运行时读取注解信息:

Class<Account> cls = Account.class;

// 读取类上的注解
if (cls.isAnnotationPresent(ApiInfo.class)) {
    ApiInfo info = cls.getAnnotation(ApiInfo.class);
    System.out.println("作者:" + info.author());
    System.out.println("版本:" + info.version());
    System.out.println("描述:" + info.description());
}

// 读取方法上的注解
for (Method method : cls.getMethods()) {
    if (method.isAnnotationPresent(ApiInfo.class)) {
        ApiInfo info = method.getAnnotation(ApiInfo.class);
        System.out.println(method.getName() + ": " + info.description());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这就是 Spring 框架实现 @Autowired、@RequestMapping 等注解功能的底层原理。

# 15.4.1 综合案例:简易字段校验器

本案例用自定义注解+反射实现字段校验,展示注解和反射结合的经典用法。

import java.lang.annotation.*;
import java.lang.reflect.*;

/**
 * 简易字段校验器 —— 注解+反射综合案例
 * 知识点:自定义注解定义、反射读取注解、运行时处理
 */

// 自定义注解:非空校验
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface NotNull {
    String message() default "不能为空";
}

// 自定义注解:范围校验
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Range {
    int min();
    int max();
    String message() default "超出范围";
}

class User {
    @NotNull(message = "用户名不能为空")
    String name;

    @Range(min = 0, max = 150, message = "年龄必须在0-150之间")
    int age;

    @NotNull
    String email;

    User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
}

class Validator {
    static void validate(Object obj) throws Exception {
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            Object value = field.get(obj);

            // 处理 @NotNull
            if (field.isAnnotationPresent(NotNull.class)) {
                NotNull nn = field.getAnnotation(NotNull.class);
                if (value == null || (value instanceof String && ((String) value).isEmpty())) {
                    System.out.println("  ✗ " + field.getName() + ": " + nn.message());
                    continue;
                }
            }

            // 处理 @Range
            if (field.isAnnotationPresent(Range.class)) {
                Range range = field.getAnnotation(Range.class);
                if (value instanceof Integer) {
                    int val = (Integer) value;
                    if (val < range.min() || val > range.max()) {
                        System.out.println("  ✗ " + field.getName() + ": " + range.message()
                                + " (值=" + val + ")");
                        continue;
                    }
                }
            }

            System.out.println("  ✓ " + field.getName() + " = " + value);
        }
    }
}

public class FieldValidatorDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("===== 简易字段校验器 =====\n");

        System.out.println("--- 有效用户 ---");
        Validator.validate(new User("张三", 25, "zhang@mail.com"));

        System.out.println("\n--- 无效用户 ---");
        Validator.validate(new User("", 200, null));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

运行结果:

===== 简易字段校验器 =====

--- 有效用户 ---
  ✓ name = 张三
  ✓ age = 25
  ✓ email = zhang@mail.com

--- 无效用户 ---
  ✗ name: 用户名不能为空
  ✗ age: 年龄必须在0-150之间 (值=200)
  ✗ email: 不能为空
1
2
3
4
5
6
7
8
9
10
11

# 15.4.2 注解反射结合训练题

训练1:创建 @Range(min, max) 注解用于标注 int 字段的合法范围,编写 validate(Object obj) 方法通过反射读取注解并校验字段值是否在范围内。

训练2:创建 @JsonField(name) 注解,编写 toJson(Object obj) 方法,将对象转换为简单的 JSON 字符串。字段名使用注解指定的名称,没有注解的字段使用原始字段名。

思考:Spring Boot 的 @SpringBootApplication 注解背后包含了 @Configuration、@EnableAutoConfiguration、@ComponentScan 三个注解。这种"注解叠加"的机制在 Java 中如何实现?

# 15.5 反射的优缺点

优点 缺点
运行时动态操作,灵活性极高 性能开销大(比直接调用慢)
是框架和库的基础 破坏封装性(可以访问私有成员)
实现通用工具和组件 代码可读性降低
支持插件化架构 编译期无法检查类型安全

建议:在业务代码中尽量少用反射,在框架和底层工具开发中合理使用。

反射性能优化策略:

// 策略1:缓存 Class、Method、Field 对象(避免重复查找)
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();

public static Object invoke(Object obj, String methodName) throws Exception {
    Method method = METHOD_CACHE.computeIfAbsent(methodName, name -> {
        try {
            Method m = obj.getClass().getDeclaredMethod(name);
            m.setAccessible(true);
            return m;
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    });
    return method.invoke(obj);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 策略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 mh = lookup.findVirtual(
    String.class, "length", MethodType.methodType(int.class));
int len = (int) mh.invoke("Hello");  // 5
// MethodHandle 可以被 JIT 内联优化,性能远优于反射
1
2
3
4
5
6
7
8
9
10
// 策略3:使用 VarHandle(JDK 9+,替代反射访问字段)
import java.lang.invoke.VarHandle;
import java.lang.invoke.MethodHandles;

// VarHandle 提供了类型安全的字段访问,性能优于反射
1
2
3
4
5

反射性能对比(大致数据):

操作方式 相对耗时
直接调用 1x(基准)
MethodHandle ~1.5x
反射(缓存后) ~5-10x
反射(未缓存) ~50-100x

# 15.5.1 综合案例:反射性能测试

本案例对比直接调用、反射调用和 MethodHandle 的性能差异。

import java.lang.invoke.*;
import java.lang.reflect.*;

/**
 * 反射性能测试 —— 反射优缺点综合案例
 * 知识点:反射开销、缓存优化、MethodHandle
 */
public class ReflectionBenchmark {

    public int add(int a, int b) { return a + b; }

    public static void main(String[] args) throws Throwable {
        System.out.println("===== 反射性能测试 =====\n");
        ReflectionBenchmark obj = new ReflectionBenchmark();
        int iterations = 1_000_000;

        // 1. 直接调用
        long start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            obj.add(1, 2);
        }
        long directTime = System.nanoTime() - start;

        // 2. 反射调用(每次查找)
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            Method m = obj.getClass().getMethod("add", int.class, int.class);
            m.invoke(obj, 1, 2);
        }
        long reflectTime = System.nanoTime() - start;

        // 3. 反射调用(缓存 Method)
        Method cached = obj.getClass().getMethod("add", int.class, int.class);
        cached.setAccessible(true);
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            cached.invoke(obj, 1, 2);
        }
        long cachedTime = System.nanoTime() - start;

        // 4. MethodHandle
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle mh = lookup.findVirtual(ReflectionBenchmark.class, "add",
                MethodType.methodType(int.class, int.class, int.class));
        start = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            mh.invoke(obj, 1, 2);
        }
        long mhTime = System.nanoTime() - start;

        System.out.printf("  直接调用      : %6.2f ms (1.0x)%n", directTime / 1e6);
        System.out.printf("  反射(未缓存)  : %6.2f ms (%.1fx)%n", reflectTime / 1e6, (double) reflectTime / directTime);
        System.out.printf("  反射(缓存)    : %6.2f ms (%.1fx)%n", cachedTime / 1e6, (double) cachedTime / directTime);
        System.out.printf("  MethodHandle  : %6.2f ms (%.1fx)%n", mhTime / 1e6, (double) mhTime / directTime);

        System.out.println("\n--- 使用建议 ---");
        System.out.println("  框架开发: 合理使用反射(缓存 Method/Field)");
        System.out.println("  高性能场景: 考虑 MethodHandle");
        System.out.println("  业务代码: 尽量避免反射");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 15.5.2 反射优缺点训练题

训练1:反射最大的性能开销来自哪里?为什么缓存 Method 对象能显著提升性能?

训练2:反射可以访问 private 成员,这是否违反了面向对象的封装原则?框架(如 Spring)为什么需要这个能力?

思考:Java 9 引入了模块系统(JPMS),对反射的 setAccessible(true) 做了更严格的限制。这对框架开发有什么影响?

上次更新: 2026/06/10, 11:13:41
泛型
README

← 泛型 README→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式