正着走是「照图纸造对象」(编译期一切已知),反射是拿着对象反查图纸——运行时拿到 Class 对象,类的字段、方法、构造方法全能拆开看,框架的命根子。
1. 为什么需要反射:new 有个致命前提
先看正常的写法(4.2 到 8 章一直在用的):
Person p = new Person(); // 编译期就认识 Person,一切尽在掌握
p.study(); // 方法名拼错?编译器当场红叉
new 的前提是:类名必须提前写死在代码里。 这在前九章毫无问题,但有一种场景它会卡死:
程序启动时读一个配置文件,文件里写着
personClass=com.demo.Person——类名是运行时才拿到的字符串。你怎么写new?字符串可不能直接 new。
String className = 配置文件里读出来的; // "com.demo.Person"
??? obj = new className(); // ✗ 编译报错,字符串不是类名
这就是反射出场的地方——运行时才拿到类名,也能把这个类拆开、用起来。
Spring 的 @Component 自动装配、MyBatis 的 Mapper、JUnit 跑你写的测试类……全是「配置/注解里给个类名字符串,框架运行时反射造对象、调方法」。学反射不是炫技,是看懂框架的前提。
2. 反射的核心角色:Class 对象(类的说明书)
Java 里每个类被加载进 JVM 时,JVM 会给它造一份唯一的「类说明书」——一个 Class 类型的对象:
- 说明书里记着:这个类叫什么、有哪些字段、哪些方法、哪些构造方法;
- 同一个类全 JVM 只有一份说明书(new 一万个 Person,说明书还是同一份);
- 反射的一切操作,都从拿到这份说明书开始。
new Person() 造的是人;Person.class 拿到的是关于 Person 这个类的说明书。前者是一万个独立个体,后者全类只有一份。Class 本身也是一个类(java.lang 包,不用 import),但它描述的是「类」而不是「东西」。
3. 三把钥匙:获取 Class 对象
同一个类,三条路拿到的是同一个 Class 对象(用 == 比全是 true):
| 方式 | 写法 | 适用场景 | 备注 |
|---|---|---|---|
| ① Class.forName | Class.forName("com.demo.Person") |
类名是运行时才拿到的字符串(配置文件、框架)——反射主用法 | 参数是全类名(带包名);抛受检异常 ClassNotFoundException |
| ② 类名.class | Person.class |
编译期就知道类名;当参数传「类型说明书」 | 最安全,不抛异常 |
| ③ 对象.getClass() | p.getClass() |
手里已有对象,反查它的出身 | Object 的原生方法,人人自带 |
三种方式的验证:
Class<?> c1 = Class.forName("com.demo.Person"); // 字符串路
Class<?> c2 = Person.class; // 类名路
Person p = new Person();
Class<?> c3 = p.getClass(); // 对象路
System.out.println(c1 == c2); // true
System.out.println(c2 == c3); // true —— 同一份说明书
Class.forName 抛的 ClassNotFoundException 是受检异常——编译器当场逼你 throws 或 try-catch。这是 6.6 ParseException 之后,受检异常的又一次实战登场。类名字符串写错(少包名、拼错字)就是这异常最常见的来源。
4. 拿到说明书后,能干什么(本章后续逐个解锁)
Class<?> c = Class.forName("com.demo.Person");
System.out.println(c.getName()); // 全类名 com.demo.Person
System.out.println(c.getSimpleName()); // 短名字 Person
// 没有类名也能造对象(下一节的主角):
Object obj = c.getDeclaredConstructor().newInstance();
// 看字段、看方法、连 private 都能摸(后续几节):
c.getDeclaredFields(); // 所有字段
c.getDeclaredMethods(); // 所有方法
先别急着全记——本节只需要记住:一切从 Class 对象开始,三把钥匙选一把拿到它。造对象、拆字段、调方法是后续几节的正戏。
8.1 说「泛型只活编译期,运行时都是裸的」——那运行时想拿类型信息怎么办?答案就是反射:Class 对象是运行时唯一能摸到类型信息的地方。泛型管编译期安检,反射管运行时拆解,一正一反凑成完整的类型世界。
5. 反射的代价与规矩
| 代价/规矩 | 说明 |
|---|---|
| 慢 | 反射调用比直接调用慢(编译器没法优化),框架内部有大量优化,业务代码别拿反射当常规手段 |
| 破坏封装 | 连 private 都能摸——这是「后门」不是「正门」,工具和框架用它,你的业务代码不该用 |
| 失去编译期检查 | 方法名字符串写错了,编译器不管,运行时才炸(5.1 三类错误里最晚暴露的那种) |
一句话:反射是给框架和工具用的手术刀,不是给业务代码用的螺丝刀。
6. 动手环节
Demo01:三把钥匙与说明书
package com.demo;
// 1. 准备一个类
class Person {
private String name;
public Person() {
System.out.println("Person 空参构造执行了");
}
public void study() {
System.out.println(name + " 在学习");
}
}
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException {
// 2. 三把钥匙
Class<?> c1 = Class.forName("com.demo.Person"); // ① 字符串路(注意全类名)
Class<?> c2 = Person.class; // ② 类名路
Class<?> c3 = new Person().getClass(); // ③ 对象路
// 3. 验证:同一份说明书
System.out.println(c1 == c2); // true
System.out.println(c2 == c3); // true
// 4. 说明书上的基本信息
System.out.println(c1.getName()); // com.demo.Person
System.out.println(c1.getSimpleName()); // Person
}
}
实验 1(30 秒):把 Class.forName("com.demo.Person") 的参数改成 "Person"(不带包名)再跑——亲眼见 ClassNotFoundException,记住全类名三字。
实验 2(30 秒):在 main 里连续 new Person() 三次,分别 getClass() 后用 == 两两比较——三次都是 true,体会「对象可以有无数个,说明书只有一份」。
实验 3(30 秒):打印 int.class 和 String.class——基本类型也有说明书(int 的 Class 是 int,不是 Integer),以后拆 ArrayList<Integer> 的泛型时会再遇到。
7. 常见误区
不是。Person.class 是类的说明书(全类一份);new Person() 造的是具体的人(随便多少个)。反射先拿说明书,再决定造不造人。
Class.forName("Person") 直接炸 ClassNotFoundException。参数必须是全类名(带包名),这是头号新手错。
同一个类全 JVM 只有一份 Class 对象,三条路拿到的用 == 比全是 true(面试常考)。
反射比直接调用慢一截,还绕过编译期检查。它是框架的底层手段,不是业务代码的常规姿势。
能摸是能力,不是许可。业务代码里随意摸 private 会把 3.3 封装性辛苦建好的规矩全拆了。知道它「能」即可。
8. 自测题
new 要求类名编译期写死;反射让「运行时才拿到的类名字符串」也能拆类、造对象、调方法。框架(Spring/JUnit/MyBatis)全靠这一手。
Class.forName(全类名)——因为只有它接收字符串,而配置文件里读出来的正是字符串;另外两种都要求编译期就知道类名。
它抛的 ClassNotFoundException 是受检异常(5.2):类名字符串是外部数据,编译器无法保证它正确,所以强制你写预案。和 6.6 parse 抛 ParseException 同款待遇。
true。说明同一个类全 JVM 只有一份 Class 对象——说明书不是跟着对象走的,是跟着类走的。
不行——new 需要编译期类名,这里只有字符串。反射分三步:① Class.forName("com.demo.UserService") 拿说明书;② 用说明书的构造方法造对象(下一节);③ 按需调方法/填字段(后续几节)。这正是框架自动装配的最小雏形。
9. 下一步学什么
- ☑ 9.1 反射概述 ✅ —— 你在这里。
- ☑ 9.2 Class类和Class对象 ✅ —— 点此复习:Class 类本身也是个类(构造器私有、JVM 造)、四大族 get 方法、Declared 规律。
相关笔记
- 第一章 Java开发入门/1.4 Java程序的运行机制:JVM 加载类时造出 Class 对象——反射的物理基础
- 第五章 异常/5.2 运行时异常与编译时异常:ClassNotFoundException 是受检异常实战
- 第六章 Java API/6.6 日期和时间类:parse 的 ParseException 同款「外部数据必受检」待遇
- 第八章 泛型/8.1 泛型基础:类型擦除的另一半真相——运行时摸类型信息全靠反射
- 第三章 面向对象(上)/3.3 封装性:private 的规矩,反射「能摸但别乱摸」的后门