Java 反射的10个使用经验
开场先划个重点,甭管你现在是 CRUD 王者,还是刚写完个 helloWorld,只要你要写框架、搞工具类、整 ORM、搞 AOP、玩注解处理,绕不开反射这碗酒。酒是好酒,就是喝多了晕,一不小心把自己干躺下。咋喝、咋稳、咋省劲,听我一条一条给你唠。
1、私有变量?我偏改,就问你服不服
你以为加个 private 就神仙打不动?Java 说:我反射了解一下啊哥。
public class LaoWang {
private String secret = "我藏得很深";
public String getSecret() {
return secret;
}
}
上去就是一顿硬刚:
LaoWang obj = new LaoWang();
Field field = obj.getClass().getDeclaredField("secret");
field.setAccessible(true); // 直接掀桌
field.set(obj, "没藏住吧");
System.out.println(obj.getSecret()); // 输出:没藏住吧
这玩意儿不是不能用,关键看你拿来干啥。要是你在那种 BeanUtils 工具里想偷着改一下还行,要是放生产核心逻辑里天天反射搞字段,运维得拿键盘削你。
2、构造方法不给你 new?我硬给你开个门
好多第三方框架或者安全封装类,构造方法藏得死死的,外面想 new 都没门。这时候你就得使点“强扭的瓜”,甭管甜不甜,先扭出来再说。
public class LaoLi {
private LaoLi(String name) {
System.out.println("偷偷 new 了老李:" + name);
}
}
反手一个反射捅进去:
Constructor<LaoLi> constructor = LaoLi.class.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 直接撬锁
LaoLi laoLi = constructor.newInstance("小李子");
说实话,这操作看着邪门,但是真有它妙用的时候,比如你想搞个对象池、Bean 工厂或者注解扫描初始化,不这么干都没法玩。别天天一来就 new,这种偷偷搞个私生子的思路,香着呢。
3、动态调用方法,别死盯那堆 if else
想做个工具类、写个统一调度、实现个通用 API?方法名、参数啥都动态的,手写 switch case?那是搬砖,不是搞技术。
public class LaoZhao {
public void sayHello(String name) {
System.out.println("老赵打招呼:" + name);
}
}
改成反射调用,灵活得一批:
LaoZhao obj = new LaoZhao();
Method method = obj.getClass().getMethod("sayHello", String.class);
method.invoke(obj, "大兄弟");
当然喽,方法一多、类一杂,别傻乎乎每次都去反射一遍,Method 对象是能缓存的,内存多点都比你 CPU 卡死强。再一个,invoke()
里包异常,调试时候容易脑壳疼,记得 unwrap 异常处理清清爽爽。
4、注解 + 反射,不搞你注定写不了框架
这年头,注解到处都是,从 SpringBoot 到自定义框架,没点反射解析能力你咋往下写?
先造个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ZhuYao {
String value();
}
配个带注解的类:
public class LaoSun {
@ZhuYao("用户名")
private String username;
@ZhuYao("密码")
private String password;
}
来,解析它:
for (Field field : LaoSun.class.getDeclaredFields()) {
if (field.isAnnotationPresent(ZhuYao.class)) {
ZhuYao anno = field.getAnnotation(ZhuYao.class);
System.out.println("字段:" + field.getName() + ",注解值:" + anno.value());
}
}
别觉得写框架是啥高深活儿,其实就这些小技巧拼拼凑凑,封装封装,外面包个壳,你就能吹说自己搞了个注解驱动的自动注册处理器,听着不唬人?
5、反射慢?那真不是假的
别看反射香,但性能是真不咋地。你单次用用没事儿,上了高并发场景还反射一万次,你这不是抡着锤子敲服务器 CPU 嘛。
搞个缓存再说话:
private static final Map<String, Method> methodCache = new HashMap<>();
public static Object invokeCachedMethod(Object obj, String methodName, Class<?>[] paramTypes, Object[] args) throws Exception {
String key = obj.getClass().getName() + "#" + methodName;
Method method = methodCache.get(key);
if (method == null) {
method = obj.getClass().getMethod(methodName, paramTypes);
methodCache.put(key, method);
}
return method.invoke(obj, args);
}
缓存啥意思?就跟你小卖部备点货一样,常用的先放边上,别老来回开门去仓库取,累不累啊?
阶段总结一哈
行了,今天先撂这五条,各种“反射的骚操作”你也瞄一眼了。你是不是也跟我当年似的,头铁非要动态调用、非要私有字段操作、非要搞全自动注解处理?别急,工具是把刀,用得巧才是本事,用得猛就把自己剁手了。
等你喊个“继续”,咱给你上剩下的五条,那才是硬货里的硬货,写框架的命根子,踩坑的祖传祸害。
等你开口,咱再继续嗷~
6、类型擦除下的反射,别想着泛型还能保留
你以为泛型能在反射里读出来?天真了兄弟,Java 的泛型,编译完就没了,擦没了!你手贱反射个 List<String>
出来,拿到的是个啥?嗯,是 List,跟 String 一毛钱关系都没有。
看这个例子你就明白:
public class LaoMa<T> {
public T data;
}
你反射看看 data
的类型:
Field field = LaoMa.class.getDeclaredField("data");
System.out.println(field.getType()); // class java.lang.Object
看清了没?泛型全整没了,就剩个 Object,典型“编译期看起来高大上,运行期啥都不是”。
要真想拿泛型信息?得从父类的 Type 里抠,这都属于“反射深水区”了,不是写几行代码就能拿下的,属于硬活儿,得慢慢摸、慢慢配。
7、代理对象反射,真不是你想当然那么简单
你以为拿个对象就能随便 getClass?那你可别忘了,Spring 那套 AOP、动态代理,把原对象给你包三层八层,结果你反射来反射去,压根不是你想反的那个。
Object bean = applicationContext.getBean("userService");
System.out.println(bean.getClass()); // com.sun.proxy.$Proxy123 或 CGLIB 动态生成的类
你用反射 getDeclaredMethod,直接 null,啥都拿不着。为啥?因为你反的是代理类,不是原类,方法都不在那儿!
解决办法呢,要么你用接口干活(JDK 动态代理还能对接口起作用),要么你用 AOPUtils、ReflectionUtils 之类的工具类再往里扒——这就不只是反射本身的活了,是反射 + 框架 + 黑魔法三合一,走错一步,全线炸穿。
8、class.forName() 别乱用,类加载是个深坑
很多人一写反射就来一句:
Class<?> clazz = Class.forName("com.xxx.LaoHei");
你还真以为这么一行代码只是“获取类对象”?老弟,这玩意儿顺带会触发类初始化!
也就是说,你静态代码块要是写了点啥乱七八糟的操作,这句代码一来,啪的一下全执行了。
public class LaoHei {
static {
System.out.println("我被加载啦!");
}
}
你反射调用:
Class<?> clazz = Class.forName("com.xxx.LaoHei");
// 控制台输出:我被加载啦!
你要是真不想触发初始化?可以用另一个构造法:
Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("com.xxx.LaoHei");
这个只是加载 class 信息,不触发 static 块执行,适合你那种“提前预热,不触发副作用”的场景。懂点这玩意儿,关键时候不踩坑,能救命。
9、数组的反射,跟普通类压根不是一路人
你要以为数组也是 Class,那你就傻了吧,数组在反射体系里是个怪胎,它的类名、类型结构、数组长度、元素类型都得另外搞。
int[] arr = new int[5];
Class<?> arrClass = arr.getClass();
System.out.println(arrClass.getName()); // [I
你要想反射设置数组值:
int[] nums = new int[3];
Array.set(nums, 0, 42);
int val = (int) Array.get(nums, 0);
System.out.println(val); // 42
这玩意儿你看着不难,其实没人告诉你压根不会想到原来还能这么搞。特别是搞那种多维数组,反射能让你头发一晚上掉光。总结:数组要反射,得用 java.lang.reflect.Array
,不是用 Field、Method 那套。
10、破坏单例?反射可以,玩脱也真快
有些人脑袋一热,非要用反射破坏单例,说能绕过构造器私有,搞出两个实例。是的,的确可以,但你真要在项目里干这事儿,我劝你早点跑路,架构师要追着你削了。
拿最典型的饿汉式单例举个例子:
public class LaoDan {
private static final LaoDan instance = new LaoDan();
private LaoDan() {}
public static LaoDan getInstance() {
return instance;
}
}
你用反射搞:
Constructor<LaoDan> cons = LaoDan.class.getDeclaredConstructor();
cons.setAccessible(true);
LaoDan one = cons.newInstance();
LaoDan two = cons.newInstance();
System.out.println(one == two); // false,直接破坏了单例
你看着是成功了,实则你破坏了别人设计的约束。现在很多类内部会检测是否已被构造过,比如通过 flag、或者直接在构造器中抛异常,防止你搞幺蛾子。也有用 Enum 来实现单例的,那更反不动了。你真非要破它,那不是技术问题,是道德问题,懂?
最后的唠叨:技术是把刀,怎么用全看人
你说反射香不香?我说它是把开山斧,能一刀斩妖除魔,也能把自己大腿劈断。你用它写框架、自动注入、注解解析,那是真厉害;你要乱来,把代码搞得别人看不懂、性能炸穿,那就真是“工具没错,人有病”。
你今天学到的这 10 招,都是我啃项目、熬通宵、和 bug 大战三百回合后总结出来的。你要真想把反射玩明白,不光得会写代码,还得会分析框架,知道 JVM 底层那些事儿。
技术不靠背语法靠干,别纸上谈兵。要是你哪天真靠反射写了个能通杀业务场景的工具,记得回来给哥留言,咱们一块喝顿酒,庆祝你“反射入门”!