跳转至

19 反射

本章目标

  • 理解反射:运行时动态查看/调用类信息
  • 掌握 Class 对象的获取
  • 掌握反射读取字段、方法、构造器
  • 了解反射在模组里的应用

19.1 什么是反射?

反射(Reflection):程序运行时动态地查看类的信息(有哪些字段、方法),并动态地调用它们——即使编译期完全不知道这个类。

正常:代码里写死 new 某个类 → 编译期就确定
反射:拿到一个 Class 对象 → 运行时才知道是什么类、有什么方法
// 正常方式
Player p = new Player("小明");
p.getLevel();

// 反射方式
Class<?> clazz = Class.forName("com.example.Player");
Object obj = clazz.getDeclaredConstructor(String.class).newInstance("小明");
Method m = clazz.getMethod("getLevel");
int level = (int) m.invoke(obj);

19.2 获取 Class 对象(三种方式)

// 方式一:类名.class
Class<String> c1 = String.class;

// 方式二:对象.getClass()
String s = "hello";
Class<?> c2 = s.getClass();

// 方式三:Class.forName(按类名,常用于动态加载)
Class<?> c3 = Class.forName("java.lang.String");

三种方式拿到的是同一个 Class 对象:

c1 == c2 && c2 == c3;    // true

19.3 查看类信息

Class<?> clazz = Player.class;

clazz.getName();           // 完整类名: com.example.Player
clazz.getSimpleName();     // 简单类名: Player
clazz.getSuperclass();     // 父类 Class
clazz.getModifiers();      // 修饰符标志位
Modifier.isPublic(clazz.getModifiers());   // 是否 public
clazz.getInterfaces();     // 实现的接口数组

19.4 反射字段(Field)

public class Player {
    private String name = "小明";
    public int level = 30;
}

Class<?> clazz = Player.class;

// 读取字段
Field nameField = clazz.getDeclaredField("name");   // 包括 private
nameField.setAccessible(true);                      // 暴力访问 private
Player p = new Player();
String name = (String) nameField.get(p);            // "小明"

// 修改字段
nameField.set(p, "小红");

// 获取所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
    System.out.println(f.getName() + " : " + f.getType().getSimpleName());
}

19.5 反射方法(Method)

Class<?> clazz = Player.class;
Player p = new Player();

// 获取并调用方法
Method m = clazz.getMethod("getLevel");       // 公开方法
int level = (int) m.invoke(p);                // 调用

// 带参数的方法
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(p, "小红");

// private 方法
Method secret = clazz.getDeclaredMethod("secretMethod");
secret.setAccessible(true);
secret.invoke(p);

19.6 反射构造器(Constructor)

Class<?> clazz = Player.class;

// 无参构造
Player p1 = (Player) clazz.getConstructor().newInstance();

// 带参构造
Player p2 = (Player) clazz.getConstructor(String.class, int.class)
        .newInstance("小明", 30);

// 或者简单点
Player p3 = (Player) clazz.getDeclaredConstructor(String.class).newInstance("小红");

19.7 反射读取注解

配合第 15 章的注解,这是框架最常见的用法:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequireAdmin { boolean value() default true; }

// 扫描一个类的所有方法,找出带注解的
public class CommandRegistry {
    public static void register(Object instance) {
        for (Method m : instance.getClass().getDeclaredMethods()) {
            if (m.isAnnotationPresent(RequireAdmin.class)) {
                RequireAdmin ra = m.getAnnotation(RequireAdmin.class);
                System.out.println("方法 " + m.getName() + " 需要管理员: " + ra.value());
            }
        }
    }
}

19.8 反射的坑与性能

反射的缺点

  1. 性能差:比直接调用慢几十倍(大量使用时注意)
  2. 破坏封装:setAccessible 能访问 private,要谨慎
  3. 代码难读:编译期检查失效,出错在运行时才发现
  4. 容易 NoSuchMethodException:方法名拼错、参数类型不匹配

原则:能用正常代码就不用反射。反射用于框架层、插件系统、兼容层。

19.9 模组里的反射

Minecraft 模组开发里反射出现在:

  1. 访问 Minecraft 私有字段/方法(Forge 用 Access Transformer / Mixin 更优雅)
  2. 兼容其他模组(检测某个模组类是否存在、调用它的 API)
  3. Forge 的注册系统内部(@Mod 注解扫描、事件注册,框架内部就是靠反射)
// 检测其他模组是否存在(经典用法)
try {
    Class.forName("com.someothermod.Main");
    System.out.println("检测到其他模组,启用兼容功能");
} catch (ClassNotFoundException e) {
    System.out.println("未安装该模组,跳过兼容");
}

Mixin 是什么

现代模组开发(尤其 NeoForge 1.20+)常用 Mixin 在字节码层面修改 Minecraft 的类。它比反射更强(能改方法体),但也更复杂。先掌握反射理解"动态修改"思想,以后学 Mixin 会轻松很多。

19.10 小练习

  1. 用三种方式获取 String 的 Class 对象,验证是同一个
  2. 反射读取一个类的所有字段名和类型
  3. 反射调用一个 private 方法(记得 setAccessible)
  4. 定义注解 @Important(RUNTIME),扫描类里带该注解的方法并打印

本章小结

  • 反射 = 运行时动态查看/调用类成员
  • 获取 Class:类名.class / getClass() / Class.forName
  • Field / Method / Constructor 配合 setAccessible 访问 private
  • 注解 + 反射 = 框架核心套路
  • 性能差、破坏封装,能不用就不用;框架层很常见

下一章:20 常用设计模式

🏠 返回总站