用买火车票的例子讲解Java反射的作用

1 Java反射的主要用途

Java反射的主要用途包括:

  1. 动态类加载和实例化

    • 运行时加载类:Class.forName("全类名")
    • 创建对象实例:clazz.newInstance() 或通过构造器创建
  2. 运行时类型检查

    • 获取类信息:字段、方法、构造器、注解等
    • 检查类结构,实现动态代理
  3. 动态调用方法

    • 通过Method.invoke()调用任意方法
    • 支持私有方法的访问(需设置setAccessible(true)
  4. 框架和工具开发

    • Spring框架:依赖注入、AOP、Bean管理
    • MyBatis:ORM映射、动态SQL
    • JUnit:测试用例发现和执行
    • 序列化库:JSON/XML转换
  5. 通用代码处理

    • 编写通用工具类,如对象拷贝、属性映射
    • 实现插件化架构,动态加载功能模块

典型应用场景:

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 反射在此场景的优势总结

  1. 解除耦合 :购票逻辑不需要知道 具体有哪些票种。它只面对Ticket抽象和类名字符串。新增票种时,主代码无需重新编译。
  2. 极致灵活 :票种列表(ticketsBought)可以来自配置文件、数据库或用户界面 。明天你想增加"观光座票",只需要:
    • 新建SightseeingSeat类。
    • 在配置清单里加入它的类名。
    • 无需修改、重新编译ReflectionBooking类。
  3. 框架基石 :想象一个购票框架 。航空公司、铁路公司用自己的票类实现Ticket接口,然后注册到框架。框架的核心出票引擎只用一套反射代码,就能为所有公司服务。

输出结果(两种方式相同):

复制代码
一等座出票完成,座位已锁定。
二等座出票完成,座位已锁定。
软卧出票完成,铺位已分配。
硬卧出票完成,铺位已分配。

最后请注意 :反射虽然强大,但也有性能开销和安全性考虑。在实际"售票系统"中,可能会用工厂模式Spring容器 (其底层基于反射)来平衡灵活性与效率,但它们的核心思想------将"是什么"和"怎么做"分离------与反射一脉相承。

相关推荐
程序员爱钓鱼2 小时前
Go高性能缓冲IO详解: bufio包深度指南
后端·面试·go
小则又沐风a2 小时前
第一章:C++入门基础--- c++入门门槛高? 逐步剖析c++语法 成为c++大师
开发语言·c++
H_老邪2 小时前
mysql 存储过程
java·数据库·sql
老虎06272 小时前
Netty[ NIO 核心速成 ] ---- NIO三大组件(Channel & Buffer&selector)
java·github·nio
熙胤2 小时前
Spring Boot 3.x 引入springdoc-openapi (内置Swagger UI、webmvc-api)
spring boot·后端·ui
小鸡吃米…2 小时前
Python 中的并发 —— 进程池
linux·服务器·开发语言·python
小王不爱笑1322 小时前
Java 异常全解析:从原理到实战,搞定异常处理
java·开发语言
tumeng07112 小时前
springboot项目架构
spring boot·后端·架构
历程里程碑2 小时前
40 UDP - 2 C++实现英汉词典查询服务
linux·开发语言·数据结构·c++·ide·c#·vim