什么是反射

Class 是「类的说明书」,反射就是拿着这本说明书,在程序运行时查看这个类、创建它、找它的方法和属性,然后动态操作它。


一、先看不用反射的情况

public class Person {
 
    private String name = "张三";
 
    public void sayHello() {
        System.out.println("你好,我是" + name);
    }
 
    public void eat(String food) {
        System.out.println("我正在吃" + food);
    }
}

平时就这么用:

Person person = new Person();
person.sayHello();// 你好,我是张三
person.eat("苹果");

关键特点:写代码的时候我就已经知道这个类叫 Person,也知道它有 sayHello()。


二、如果事先不知道这个类呢

比如需求是:用户输入一个类名,程序自动创建这个类的对象。

请输入类名:
Person

问题在于,编译的时候我不知道用户以后会输入什么,可能是 Student、Dog、Cat、Order、User……总不能写一长串判断:

if (name.equals("Person")) {
    Person p = new Person();
} else if (name.equals("Student")) {
    Student s = new Student();
} else if (...) {
    // 没完没了
}

于是反射登场。


三、反射的核心:Class

Java 提供了 Class 类,可以把它理解成某一个 Java 类的说明书 / 身份证。

Class<Person> clazz = Person.class;
System.out.println(clazz.getName());// Person

一本说明书里能翻到这些内容:

  • 类名、父类、实现的接口
  • 构造方法
  • 普通方法
  • 成员变量

拿到 Class 对象有三种方式:

写法适用场景
Person.class编译期就知道类名
person.getClass()手里已经有这个类的对象
Class.forName("com.demo.Person")只有一个字符串,反射的关键入口

Class.forName() 里要写全限定类名(包名 + 类名)。如果类是写在默认包里的,写 "Person" 也能跑,但实际项目里类都在包里,所以下面统一用 com.demo.Person。


四、只用字符串也能拿到类的信息

这才是反射真正有意思的地方。我现在手里只有一个字符串:

String className = "com.demo.Person";
Class<?> clazz = Class.forName(className);
System.out.println(clazz.getName());
"com.demo.Person"
      ↓  Class.forName()
   Class 对象
      ↓
我开始知道这个类的信息

这就是运行时获取类的信息。


五、Class 对象上能问什么

Class<?> clazz = Class.forName("com.demo.Person");
 
for (Method method : clazz.getDeclaredMethods()) {
    System.out.println(method.getName());
}
// sayHello
// eat

注意,这里没有创建 Person 对象,也没有写过 new Person()。我只是拿着 Class 问了一句:Person,你有哪些方法?


六、创建对象并调用方法

Class<?> clazz = Class.forName("com.demo.Person");
Object person = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("sayHello");
method.invoke(person);   // 你好,我是张三

这四行就是很典型的反射。

person 的类型是 Object,因为写代码的时候根本不知道它到底是什么类。

两个要强调的点:

  • newInstance() 要求这个类有无参构造。只有带参构造时,要写成 clazz.getDeclaredConstructor(String.class).newInstance("张三")。
  • getDeclaredMethod("sayHello") 取的是无参方法。调带参方法要补上参数类型,比如 getDeclaredMethod("eat", String.class)。

七、正常调用和反射调用的区别

正常调用反射调用
类名和方法名写死在代码里存成字符串,可以来自配置文件、数据库、用户输入、注解
拿到对象new Person()Class.forName(...) → newInstance()
调用方法person.sayHello()getDeclaredMethod("sayHello") → invoke(person)
什么时候确定编译期运行期

两者用一句话对比:

  • 正常调用:我提前知道你叫 Person,也知道你有 sayHello 方法
  • 反射调用:我现在才知道你是谁,然后自己去你的说明书里找 sayHello,再调用它

八、反射能碰到 private

平时直接访问私有字段会编译报错:

Person person = new Person();
System.out.println(person.name);   // ❌ name 是 private

反射可以:

Object person = clazz.getDeclaredConstructor().newInstance();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);                // 关掉访问检查
System.out.println(field.get(person));    // 张三
 
field.set(person, "李四");
System.out.println(field.get(person));    // 李四

私有方法也一样:

Method method = clazz.getDeclaredMethod("secret");
method.setAccessible(true);
method.invoke(person);   // 这是我的秘密

setAccessible(true) 只是跳过了访问检查,字段本身并没有被改成 public。

所以反射一共能做六件事:

反射
 │
 ├── 看类的信息
 ├── 创建对象
 ├── 找方法
 ├── 调用方法
 ├── 找字段
 └── 修改字段

九、「动态」到底指什么

这是反射里最容易让初学者懵的词。

普通代码:

Person p = new Person();
p.sayHello();

编译之前类名和方法名就全部写死了。

反射:

String className = "com.demo.Person";
Class<?> clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("sayHello");
method.invoke(obj);

这里的 className 可以来自:

配置文件
数据库
用户输入
框架配置
注解

程序运行起来以后才决定「我要操作哪个类」。这就是动态。


十、回头看那句话

反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法。

翻译成人话就是:

  • clazz.getDeclaredFields() → 「Person,你有哪些属性?」
  • clazz.getDeclaredMethods() → 「Person,你有哪些方法?」
  • clazz.getDeclaredConstructors() → 「Person,你有哪些构造方法?」

补充一个容易混的细节:getDeclaredXxx() 拿的是本类声明的全部成员,包括 private;而 getXxx() 只拿 public 的,但包含从父类继承来的。


十一、框架为什么离不开反射

Spring

我明明没写 new UserService(),Spring 怎么知道要创建 UserService?

Spring 启动
    ↓
扫描项目中的类
    ↓
发现 @Service
    ↓
获取这个类的信息
    ↓
创建 UserService 对象
    ↓
放进 Spring 容器

这里大量用到反射 + 注解。

MyBatis

@Mapper
public interface UserMapper {
    User findById(Long id);
}

UserMapper 明明只是个接口,连实现类都没写,为什么 @Autowired 之后就能直接调?背后是动态代理 + 反射,MyBatis 帮你生成了代理对象。

Spring MVC

浏览器访问 GET /user,Spring 怎么知道它对应 getUser()?

扫描类
  ↓
找到 @GetMapping
  ↓
读取 "/user"
  ↓
记录 "/user" → getUser()
  ↓
请求来了
  ↓
找到对应方法
  ↓
反射调用

十二、面试怎么答

理解之后,答案可以压缩成三句:

Java 反射是指程序在运行时动态获取类的信息,并对类或对象进行操作的机制。

可以通过 Class 对象获取类的名称、字段、方法、构造器等信息,也可以动态创建对象、调用方法、访问字段。例如用 Class.forName() 根据类名拿到 Class 对象,再用 getDeclaredConstructor().newInstance() 创建对象,用 getDeclaredMethod() 拿到方法,最后用 invoke() 调用。

Spring、MyBatis 等框架大量使用了反射,它让框架能够在运行时根据配置、注解等信息动态创建对象和调用方法。