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 反射的坑与性能¶
反射的缺点
- 性能差:比直接调用慢几十倍(大量使用时注意)
- 破坏封装:setAccessible 能访问 private,要谨慎
- 代码难读:编译期检查失效,出错在运行时才发现
- 容易 NoSuchMethodException:方法名拼错、参数类型不匹配
原则:能用正常代码就不用反射。反射用于框架层、插件系统、兼容层。
19.9 模组里的反射¶
Minecraft 模组开发里反射出现在:
- 访问 Minecraft 私有字段/方法(Forge 用 Access Transformer / Mixin 更优雅)
- 兼容其他模组(检测某个模组类是否存在、调用它的 API)
- 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 小练习¶
- 用三种方式获取 String 的 Class 对象,验证是同一个
- 反射读取一个类的所有字段名和类型
- 反射调用一个 private 方法(记得 setAccessible)
- 定义注解 @Important(RUNTIME),扫描类里带该注解的方法并打印
本章小结¶
- 反射 = 运行时动态查看/调用类成员
- 获取 Class:类名.class / getClass() / Class.forName
- Field / Method / Constructor 配合 setAccessible 访问 private
- 注解 + 反射 = 框架核心套路
- 性能差、破坏封装,能不用就不用;框架层很常见
下一章:20 常用设计模式