1 Java反射的主要用途
Java反射的主要用途包括:
-
动态类加载和实例化
- 运行时加载类:
Class.forName("全类名") - 创建对象实例:
clazz.newInstance()或通过构造器创建
- 运行时加载类:
-
运行时类型检查
- 获取类信息:字段、方法、构造器、注解等
- 检查类结构,实现动态代理
-
动态调用方法
- 通过
Method.invoke()调用任意方法 - 支持私有方法的访问(需设置
setAccessible(true))
- 通过
-
框架和工具开发
- Spring框架:依赖注入、AOP、Bean管理
- MyBatis:ORM映射、动态SQL
- JUnit:测试用例发现和执行
- 序列化库:JSON/XML转换
-
通用代码处理
- 编写通用工具类,如对象拷贝、属性映射
- 实现插件化架构,动态加载功能模块
典型应用场景:
java
// 动态创建对象并调用方法
Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();
Method method = clazz.getMethod("setName", String.class);
method.invoke(obj, "张三");
注意事项:
- 性能开销较大(相比直接调用)
- 绕过访问权限检查,可能破坏封装性
- 编译器无法检查类型安全
反射使得Java程序在运行时能够"自省"和操作自身结构,是许多高级特性和框架的基础。
2 场景:买火车票(一等座、二等座、软卧、硬卧)
接下来用一个"买票"的场景来讲解反射的作用,先展示没有反射 的传统方式,再展示使用反射的动态方式,最后总结对比。
- 场景设定
- 一个系统中有四种票类
FirstClassSeat(一等座)SecondClassSeat(二等座)SoftSleeper(软卧)HardSleeper(硬卧)。
- 需求:用户批量购买了这四种票,系统需要为每一种票执行"出票"操作。
- 一个系统中有四种票类
2.1 传统方式(不使用反射)
票类定义:
java
// 所有票的父类或接口
abstract class Ticket {
public abstract void confirm();
}
class FirstClassSeat extends Ticket {
@Override
public void confirm() {
System.out.println("一等座出票完成,座位已锁定。");
}
}
class SecondClassSeat extends Ticket {
@Override
public void confirm() {
System.out.println("二等座出票完成,座位已锁定。");
}
}
class SoftSleeper extends Ticket {
@Override
public void confirm() {
System.out.println("软卧出票完成,铺位已分配。");
}
}
class HardSleeper extends Ticket {
@Override
public void confirm() {
System.out.println("硬卧出票完成,铺位已分配。");
}
}
购票逻辑(硬编码):
java
public class TraditionalBooking {
public static void main(String[] args) {
// 用户购买的票种列表
String[] ticketsBought = {"FirstClassSeat", "SecondClassSeat", "SoftSleeper", "HardSleeper"};
for (String ticketType : ticketsBought) {
// 必须用if/else或switch显式判断每一种类型
switch (ticketType) {
case "FirstClassSeat":
new FirstClassSeat().confirm();
break;
case "SecondClassSeat":
new SecondClassSeat().confirm();
break;
case "SoftSleeper":
new SoftSleeper().confirm();
break;
case "HardSleeper":
new HardSleeper().confirm();
break;
default:
System.out.println("未知票种");
}
}
}
}
传统方式的问题:
- 每增加一种新票种(如"无座票"),就必须修改购票逻辑 ,添加新的
case。 - 票种名称(字符串)和具体类之间的映射关系是硬编码的,不灵活。
2.2 使用反射的方式
购票逻辑(动态反射):
java
import java.lang.reflect.Constructor;
public class ReflectionBooking {
public static void main(String[] args) throws Exception {
// 用户购买的票种列表(这里可以是来自配置文件、数据库或网络请求)
String[] ticketsBought = {
"demo.FirstClassSeat",
"demo.SecondClassSeat",
"demo.SoftSleeper",
"demo.HardSleeper"
// 未来可以轻松增加: "demo.NoSeatTicket"
};
for (String className : ticketsBought) {
try {
// 1. 动态加载类: 根据字符串找到"票的蓝图"
Class<?> clazz = Class.forName(className);
// 2. 动态创建实例: 根据蓝图创建"票"这个实物
Constructor<?> constructor = clazz.getDeclaredConstructor();
Ticket ticket = (Ticket) constructor.newInstance(); // 创建具体的票对象
// 3. 动态调用方法: 让这张票执行"出票"操作
ticket.confirm();
} catch (ClassNotFoundException e) {
System.out.println("未找到票种: " + className);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
用"买票"比喻反射的核心作用
| 反射操作 | 买票比喻 | 作用 |
|---|---|---|
Class.forName("全类名") |
根据票的名称,找到对应的"售票模板"或"印刷模具"。 | 动态加载。系统不需要事先知道用户要买"软卧",当听到"软卧"这个词时,才去仓库里找它的模具。 |
clazz.newInstance() |
用找到的模具,印刷出一张实实在在的票。 | 动态创建对象。模具(类)本身不是票,用它印出来的(对象)才是票。 |
method.invoke(obj) |
对印好的票,执行"盖出票章"这个动作。 | 动态调用方法。不管是一等座还是硬卧,都执行同一个"出票"动作,但各自的效果不同。 |
| 配置文件/数据库 | 用户的购票清单。 | 将变化的部分(买哪些票)外置。清单可以随时更改,而检票系统(主代码)无需任何修改。 |
2.3 反射在此场景的优势总结
- 解除耦合 :购票逻辑不需要知道 具体有哪些票种。它只面对
Ticket抽象和类名字符串。新增票种时,主代码无需重新编译。 - 极致灵活 :票种列表(
ticketsBought)可以来自配置文件、数据库或用户界面 。明天你想增加"观光座票",只需要:- 新建
SightseeingSeat类。 - 在配置清单里加入它的类名。
- 无需修改、重新编译
ReflectionBooking类。
- 新建
- 框架基石 :想象一个购票框架 。航空公司、铁路公司用自己的票类实现
Ticket接口,然后注册到框架。框架的核心出票引擎只用一套反射代码,就能为所有公司服务。
输出结果(两种方式相同):
一等座出票完成,座位已锁定。
二等座出票完成,座位已锁定。
软卧出票完成,铺位已分配。
硬卧出票完成,铺位已分配。
最后请注意 :反射虽然强大,但也有性能开销和安全性考虑。在实际"售票系统"中,可能会用工厂模式 或Spring容器 (其底层基于反射)来平衡灵活性与效率,但它们的核心思想------将"是什么"和"怎么做"分离------与反射一脉相承。