9.2 只是「看」说明书(拿类名、数方法),这一节开始「用」说明书:反射造对象、反射拆字段、反射调方法——三件套打完,"没有类名也能操作一个类"就落成代码了,Spring 的影子也出来了。
1. 引入:上节的伏笔,这节收
9.2 结尾埋了一句话:Field / Method / Constructor 是"反射三兄弟",先混脸熟。这节三兄弟全员上岗:
| 你想干的事 | 正常写法 | 反射写法 |
|---|---|---|
| 造一个对象 | new Student("张三", 18) |
构造器 newInstance("张三", 18) |
| 读/改字段 | s.name(还得是 public) |
Field 的 get(s) / set(s, 值) |
| 调一个方法 | s.eat("米饭") |
Method 的 invoke(s, "米饭") |
左边那栏编译期就得写死类名;右边那栏——类名来自字符串,运行到一半才决定操作谁。这就是反射的全部意义。
2. 操作一:反射创建对象(最常用)
Class<?> c = Class.forName("Student"); // 拿说明书(9.1 三把钥匙)
Constructor<?> cons = c.getConstructor(String.class, int.class);
Student s = (Student) cons.newInstance("张三", 18);
三个要点:
getConstructor(参数类型...)传的是"类型",所以写String.class、int.class——告诉它你要的是哪个构造器(重载定位,2.6 的账)。newInstance(实参)才传"值",并且返回类型是Object——因为 JVM 编译时不知道你要造谁,得你自己(Student)强转收回来(4.4 多态:编译看左,运行看右)。- 无参构造可以偷懒:
c.getDeclaredConstructor().newInstance()。
newInstance() 手里只有 Object,你强转错了类型,编译器不拦,运行时直接 ClassCastException(4.4 集齐的三大新手异常之一)。这是反射的代价,不是 bug。
3. 操作二:反射操作字段
Field f = c.getDeclaredField("name"); // 拿"字段说明书"(9.2 学的)
f.setAccessible(true); // 破 private 的后门
Object v = f.get(s); // 读:张三的 name
f.set(s, "波仔"); // 写:改成"波仔"
两个要点:
get/set必须传对象——f只是"字段说明书"(类级,每类一份),而字段的值长在每个对象身上。你要读张三还是李四的 name,就得把谁传进去。setAccessible(true):碰 private 字段前必须开,否则IllegalAccessException。这叫暴力反射——3.3 封装立的规矩,反射手里有钥匙。
4. 操作三:反射调用方法
Method m = c.getDeclaredMethod("eat", String.class); // 方法名 + 参数类型
m.setAccessible(true); // 私有方法才需要
Object result = m.invoke(s, "米饭"); // 谁的方法 + 实参
和字段一个套路,注意两处参数长得像但不是一回事:
getDeclaredMethod("eat", String.class)—— 第二个参数是参数的类型(用于在重载里定位方法)m.invoke(s, "米饭")—— 这里传的才是参数的值(以及"调用谁的方法")
返回值也是 Object:方法有返回值就接住强转,没有就是 null。
5. 说明书 vs 对象:别混
(这是本节最容易懵的地方,看图↓ 看完回来继续)
- 类级(每类一份):
Class、Constructor、Field、Method—— 它们描述"这个类有什么"。 - 对象级(每人一份):字段值、方法执行 —— 它们描述"这个对象是多少、干什么"。
所以反射 API 的规矩是:操作谁,就把谁传进去。f.get(s)、m.invoke(s, ...) 里的 s 缺不得。唯一例外是 newInstance()——它就是在"造对象",所以不用传。
6. 收网旧账
- 9.1 毕业题的 Spring 雏形,现在能真写出来了:
String className = "Student"; // 实际中来自配置文件/注解,反正是个字符串
Class<?> c = Class.forName(className); // ① 字符串 → 说明书
Object obj = c.getDeclaredConstructor().newInstance(); // ② 说明书 → 对象
// ③ 后续:从 obj 拆字段、调方法、贴注解——框架初始化就这么三步
- 3.3 封装:private 是给编译器看的门禁,运行期反射手里有钥匙(
setAccessible)。 - 4.4 多态:
newInstance返回Object再强转,编译看左运行看右的又一现场。 - 2.6 方法重载:
getConstructor(类型...)/getDeclaredMethod(名, 类型...)靠参数类型在重载里精确定位。 - 6.2 字符串:
Class.forName的参数就是个字符串——"类名可以是数据",这句在 9.1 只是说法,现在是代码。
7. 动手:Demo03 反射三件套全流程
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Demo03 {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("Student");
// 一、造对象
Constructor<?> cons = c.getConstructor(String.class, int.class);
Object s = cons.newInstance("张三", 18);
System.out.println(s); // 打印走 4.5 的 toString
// 二、拆字段:连 private 都不放过
Field f = c.getDeclaredField("name");
f.setAccessible(true);
System.out.println("改前:" + f.get(s));
f.set(s, "波仔");
System.out.println("改后:" + f.get(s));
// 三、调方法:私有方法也照调
Method m = c.getDeclaredMethod("eat", String.class);
m.setAccessible(true);
m.invoke(s, "米饭");
}
}
三个 30 秒实验:
- 把
(Student)强转删掉,用Object接住newInstance的结果再调f.get(s)——照常能跑?想想为什么(f.get参数本来就是Object)。 - 注释掉
f.setAccessible(true)再跑——IllegalAccessException当场报出来,看它是不是运行时异常(5.2 家谱定位一下)。 - 把
getConstructor(String.class, int.class)改成getConstructor()跑一把——NoSuchMethodException:你没写无参构造,反射找得比编译器还较真。
8. 常见误区
- ❌
newInstance()直接当 Student 用 —— 返回的是Object,不强转后面全报"找不到成员"。先(Student)再说。 - ❌ 拿私有字段忘开
setAccessible(true)——getDeclaredField只负责"找到","碰"之前还得开后门,缺一报IllegalAccessException。 - ❌
f.get()忘传对象 ——Field是说明书不是值,不传对象它不知道读谁;错写成f.get()直接编译不过,算友好提醒。 - ❌
getMethod和invoke的参数搞混 —— 前者传"类型"(String.class),后者传"值"("米饭")。记口诀:先看长相,再干活。 - ❌ 反射万能论,处处用反射 —— 反射慢、丢编译检查、破坏封装。框架底层用是逼不得已,你的业务代码能
new就new。
9. 可折叠自测
它是 String 这个类的 Class 对象(9.1 三把钥匙之一的"类名.class"写法)。构造器有重载,反射靠"参数类型列表"精确定位你要哪一个——和 2.6 重载定位一个道理。
f 只是字段说明书(类级,一份),值存在每个对象身上(对象级,一人一份)。不传对象编译都过不了——它不知道该读谁的 name。
前者传类型(用于在重载中定位方法,看的是"长相");后者传值(真正干活时的实参)外加"调用谁的方法"。口诀:先看长相,再干活。
打破 private 门禁(暴力反射)。私有字段 f.get/set 前、私有方法 m.invoke 前都要开。它证明 3.3 说的"封装防的是编译期误用",运行期反射手里有钥匙——所以反射也要守规矩,别乱摸。
Class<?> c = Class.forName(className); // 字符串 → 说明书
Object obj = c.getDeclaredConstructor().newInstance(); // 说明书 → 对象
for (Field f : c.getDeclaredFields()) { // 全字段(含私有)
f.setAccessible(true); // 开后门
System.out.println(f.getName() + " = " + f.get(obj)); // 传对象读值
}
三步写全,你就把 9.1 毕业题的 Spring 雏形补完最后一环了。
10. 下一步学什么
- ☑ 9.4 注解 ✅ —— 点此复习:注解=贴在代码上的标签、内置三大件、自定义注解与元注解、迷你 JUnit 雏形。
相关笔记
- 第九章 反射机制和注解/9.2 Class类和Class对象:反射三兄弟 Field/Method/Constructor 的"长相"都在那节
- 第三章 面向对象(上)/3.3 封装性:private 门禁被
setAccessible撬开——反射的规矩与代价 - 第三章 面向对象(上)/3.4 构造方法:重载定位靠参数类型列表,反射里一个套路
- 第四章 面向对象(下)/4.4 多态:
newInstance返回Object强转——编译看左运行看右又一现场 - 第五章 异常/5.2 运行时异常与编译时异常:
IllegalAccessException/NoSuchMethodException都是运行时异常,不强制处理