这是一篇面向初学者的原创学习笔记。很多人第一次学到 Java 反射时,会觉得它既抽象又神秘,甚至会把它理解成“高级黑魔法”。其实反射并没有那么可怕。只要抓住它的核心:程序在运行期间获取类信息,并动态操作类的成员,你就会发现它本质上是一种非常强大的动态机制。本文将从概念、使用方式、常见 API、代码示例、优缺点和应用场景几个方面,系统讲清 Java 反射。


一、什么是反射?

先不要急着背定义,我们先用一句最容易理解的话来描述反射:

反射就是程序在运行时,去查看一个类、分析一个类、操作一个类。

正常写 Java 代码时,我们通常是在“编译期”就把一切都写死了。

例如:

Student student = new Student();
student.setName("张三");
student.show();

这里你在写代码的时候,已经明确知道:

  • 要创建的是 Student
  • 要调用的是 setName()
  • 要执行的是 show()

也就是说,这种写法是“静态”的。

但有时候,程序在编译阶段并不知道将来到底要操作哪个类,只有等程序真正运行起来之后,才知道要处理什么对象。

这时就需要反射。

反射允许我们在运行期间做这些事情:

  • 获取类的信息
  • 获取构造方法
  • 获取成员变量
  • 获取成员方法
  • 创建对象
  • 调用方法
  • 修改属性值

所以你可以把反射理解为:

Java 提供的一套“运行时解剖类结构并动态操作对象”的机制。


二、为什么要学反射?

很多初学者学反射时会问:

“平时直接 new 对象、调用方法不就行了吗?为什么还要反射?”

这个问题问得很好。

因为在一般业务代码中,确实大部分时候不需要手写很多反射代码。但是 Java 的很多底层框架、通用工具、容器机制,几乎都离不开反射。

比如:

  • Spring 通过反射创建 Bean
  • MyBatis 通过反射给对象赋值
  • JUnit 通过反射执行测试方法
  • 很多注解框架底层都要配合反射解析

也就是说:

你平时可以少写反射,但你几乎一定会使用建立在反射之上的框架。

所以学习反射的意义,不只是会写 API,更重要的是:

  • 帮你理解框架原理
  • 帮你理解“动态性”是怎么实现的
  • 为后面学习注解、代理、Spring 打基础

三、反射的核心入口:Class 类

要学反射,最重要的第一步,就是认识 Class 类。

在 Java 中,每一个类在被加载到 JVM 后,都会对应一个 Class 对象。

也就是说:

  • Student 这个类,本身有一份类信息
  • JVM 会把这份类信息封装成一个 Class 对象
  • 程序可以通过这个 Class 对象获取类的各种结构信息

所以,反射的入口通常就是先拿到某个类的 Class 对象。


四、获取 Class 对象的三种常见方式

假设有一个类:

class Student {
}

1. 通过类名.class 获取

Class<Student> c1 = Student.class;

这是最直接、最常用的一种方式。

适合:

  • 编译期已经明确知道类名

2. 通过对象.getClass() 获取

Student student = new Student();
Class<? extends Student> c2 = student.getClass();

适合:

  • 已经有对象实例时获取其运行时类型

3. 通过 Class.forName(“全类名”) 获取

Class<?> c3 = Class.forName("com.example.Student");

这是反射里很重要的一种方式。

它的特点是:

  • 通过字符串形式传入类名
  • 非常适合做配置化、动态加载

比如配置文件里写一个类名,程序运行时读取出来,再通过 Class.forName() 加载它。

这正是很多框架动态加载类的常见思路。


五、为什么说 Class 是反射的起点?

因为你拿到 Class 对象后,就能继续获取:

  • 构造器 Constructor
  • 成员变量 Field
  • 成员方法 Method
  • 类名、包名、修饰符、父类、接口等信息

简单说:

Class 就像是一个“类的说明书对象”。

有了它,后面的各种反射操作才能展开。


六、通过反射获取构造方法

先定义一个类:

class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

1. 获取所有 public 构造方法

Constructor<?>[] constructors = Student.class.getConstructors();

2. 获取所有构造方法

Constructor<?>[] constructors = Student.class.getDeclaredConstructors();

区别:

  • getConstructors():只拿 public
  • getDeclaredConstructors():拿到当前类声明的全部构造方法,包括 private

3. 获取指定构造方法

Constructor<Student> constructor = Student.class.getConstructor(String.class, int.class);

参数要和目标构造器的参数列表一一对应。


七、通过反射创建对象

拿到构造器之后,就可以创建对象。

Constructor<Student> constructor = Student.class.getConstructor(String.class, int.class);
Student student = constructor.newInstance("张三", 18);

这就相当于:

Student student = new Student("张三", 18);

只不过这里是在运行时动态完成的。

通过无参构造创建对象

Student student = Student.class.getConstructor().newInstance();

在早期代码里,你可能还见过:

Student student = Student.class.newInstance();

但这种方式现在通常不推荐了,因为它只能调用无参构造,而且可读性和异常处理都不如通过 Constructor 的方式清晰。


八、通过反射获取成员变量

继续使用 Student 类:

class Student {
    private String name;
    public int age;
}

1. 获取所有 public 成员变量

Field[] fields = Student.class.getFields();

2. 获取所有声明的成员变量

Field[] fields = Student.class.getDeclaredFields();

区别依然类似:

  • getFields():只能拿 public,而且包含父类可继承的 public 字段
  • getDeclaredFields():拿当前类自己声明的所有字段,包括 private

3. 获取指定字段

Field nameField = Student.class.getDeclaredField("name");
Field ageField = Student.class.getField("age");

九、通过反射操作成员变量

反射不仅能获取字段,还能给字段赋值、读取字段值。

class Student {
    private String name;
    public int age;
}

1. 给 public 字段赋值

Student student = new Student();
Field ageField = Student.class.getField("age");
ageField.set(student, 20);
System.out.println(ageField.get(student));

2. 给 private 字段赋值

Student student = new Student();
Field nameField = Student.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(student, "李四");
System.out.println(nameField.get(student));

这里最关键的一句是:

nameField.setAccessible(true);

它的作用是:

暂时忽略 Java 的访问权限检查,让我们可以操作 private 成员。

当然,真实开发里不要滥用这种能力,因为它破坏了封装性。


十、通过反射获取成员方法

假设有一个类:

class Student {
    public void study() {
        System.out.println("学习中");
    }

    private void sleep() {
        System.out.println("睡觉中");
    }

    public String say(String msg) {
        return "学生说:" + msg;
    }
}

1. 获取所有 public 方法

Method[] methods = Student.class.getMethods();

2. 获取所有声明的方法

Method[] methods = Student.class.getDeclaredMethods();

3. 获取指定方法

Method studyMethod = Student.class.getMethod("study");
Method sayMethod = Student.class.getMethod("say", String.class);
Method sleepMethod = Student.class.getDeclaredMethod("sleep");

注意:

  • 获取方法时,要把方法名写对
  • 如果方法有参数,要把参数类型也传进去

十一、通过反射调用方法

1. 调用 public 方法

Student student = new Student();
Method studyMethod = Student.class.getMethod("study");
studyMethod.invoke(student);

这相当于:

student.study();

2. 调用带参数的方法

Student student = new Student();
Method sayMethod = Student.class.getMethod("say", String.class);
Object result = sayMethod.invoke(student, "你好");
System.out.println(result);

输出:

学生说:你好

3. 调用 private 方法

Student student = new Student();
Method sleepMethod = Student.class.getDeclaredMethod("sleep");
sleepMethod.setAccessible(true);
sleepMethod.invoke(student);

同样,调用私有方法前需要先放开访问限制。


十二、反射中常见的几个核心类

学反射时,经常会看到下面几个类:

1. Class

表示类本身的信息,是反射入口。

2. Constructor

表示构造方法,用于创建对象。

3. Field

表示成员变量,用于读取或修改属性。

4. Method

表示成员方法,用于动态调用方法。

你可以把它们理解成:

  • Class:整本说明书
  • Constructor:怎么造对象
  • Field:对象里有哪些属性
  • Method:对象有哪些行为

十三、一个完整反射示例

下面用一个综合示例把前面的知识串起来。

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void show() {
        System.out.println("name=" + name + ", age=" + age);
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class<Student> clazz = Student.class;

        Constructor<Student> constructor = clazz.getConstructor(String.class, int.class);
        Student student = constructor.newInstance("王五", 20);

        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(student, "赵六");

        Method showMethod = clazz.getMethod("show");
        showMethod.invoke(student);
    }
}

输出:

name=赵六, age=20

这个例子完整体现了:

  • 获取 Class
  • 获取构造器创建对象
  • 获取字段并修改私有属性
  • 获取方法并执行

十四、反射的优点

1. 动态性强

程序运行时可以根据配置、条件、输入决定操作哪个类。

2. 扩展性强

很多通用框架可以不依赖某个写死的类,而是面向“类信息”编程。

3. 有利于框架设计

反射是很多 Java 框架底层的基础能力。

比如 IOC、ORM、注解解析、对象拷贝工具等,很多地方都离不开反射。


十五、反射的缺点

1. 性能比直接调用略低

因为反射多了一层动态解析。

虽然现代 JVM 对反射做了很多优化,但在高频、核心路径上,反射一般还是不如直接调用高效。

2. 破坏封装性

如果通过 setAccessible(true) 去操作 private 成员,本质上是绕过了类的访问控制。

3. 可读性差

直接调用代码一眼就能看懂,而反射代码通常更绕。

例如:

method.invoke(obj);

显然没有:

obj.show();

来得直观。

4. 容易在运行时暴露错误

比如方法名写错、字段名写错,编译器很多时候帮不了你,只能等运行时发现。


十六、反射常见应用场景

1. 框架根据配置文件创建对象

例如配置文件中写:

className=com.example.Student

程序读取后:

Class<?> clazz = Class.forName(className);
Object obj = clazz.getConstructor().newInstance();

2. 注解解析

程序通过反射获取类、方法、字段上的注解,再根据注解执行不同逻辑。

3. 通用工具类

例如对象拷贝、属性注入、Bean 映射等,很多都会通过反射批量操作字段和方法。

4. 测试框架

比如 JUnit 可以找到带测试标记的方法并自动执行。


十七、反射和普通写法的区别

你可以这样理解:

普通写法

Student student = new Student();
student.show();

特点:

  • 写法简单
  • 性能更好
  • 编译期类型明确

反射写法

Class<?> clazz = Class.forName("com.example.Student");
Object obj = clazz.getConstructor().newInstance();
Method method = clazz.getMethod("show");
method.invoke(obj);

特点:

  • 更灵活
  • 更动态
  • 更适合框架底层

所以反射不是为了替代普通写法,而是为了在“需要动态能力”的场景下提供支持。


十八、初学者最容易踩的坑

1. 把类名字符串写错

例如:

Class.forName("com.example.Stundet");

类名一旦拼错,运行时就会抛异常。

2. 获取 private 成员时忘了 setAccessible(true)

如果字段或方法是私有的,直接操作通常会报访问异常。

3. 获取方法时参数类型写错

比如方法定义是:

public void test(String s)

那你就必须这样拿:

getMethod("test", String.class)

4. 误以为反射能替代所有普通代码

反射很强大,但不是所有场景都该用。大多数业务开发中,直接写对象调用往往更合适。


十九、如何用一句话总结反射?

你可以把反射概括成下面这句:

反射就是程序在运行时,通过 Class 对象获取类结构信息,并动态创建对象、访问属性、调用方法的一套机制。

如果再简化一点,就是:

反射让程序拥有了“运行时操作类”的能力。


二十、最后总结

学反射时,不要一上来就被一堆 API 吓到。

你真正要抓住的核心只有三个层次:

第一层:知道反射是什么

反射是运行时动态操作类的机制。

第二层:知道反射能做什么

  • 获取类信息
  • 创建对象
  • 访问字段
  • 调用方法

第三层:知道反射为什么重要

因为很多 Java 框架底层都依赖反射。

所以你学反射,真正的意义不是为了在业务代码里疯狂手写反射,而是为了:

  • 看懂框架
  • 理解底层原理
  • 建立 Java 动态机制的思维

当你后面学到注解、动态代理、Spring IOC 的时候,你会发现反射的价值一下子就显现出来了。


二十一、练习题

练习 1

定义一个 User 类,包含无参构造、有参构造、私有字段 name 和公有方法 show(),然后通过反射创建对象。

练习 2

通过反射获取 User 类中的私有字段 name,并给它赋值为 张三

练习 3

通过反射调用 User 类中的 show() 方法。

练习 4

思考:为什么很多框架喜欢使用 Class.forName() 而不是直接 new 对象?

练习 5

总结:反射的优点和缺点分别是什么?

更多推荐