当前位置: 首页 > news >正文

Java 反射机制实战:对象属性复制与私有方法调用全解析

Java 反射机制实战:对象属性复制与私有方法调用全解析

反射机制不是纸上谈兵的理论,而是能解决实际开发痛点的利器。当你需要写一个通用的对象复制工具,或者不得不调用第三方类的私有方法时,反射就能大显身手。本文通过两个实战案例 ——反射实现对象属性复制反射调用私有方法,带你掌握反射在实际开发中的应用技巧,同时规避隐藏的坑。

一、实战一:反射实现对象属性复制

在开发中,经常需要将一个对象的属性值复制到另一个对象(比如 DTO 转 PO、PO 转 VO)。如果手动 get/set,不仅代码冗余,还容易遗漏字段。用反射实现一个通用的属性复制工具,能一劳永逸解决这个问题。

1. 需求分析:属性复制需要处理什么?

一个靠谱的属性复制工具,需要考虑这些场景:

  • 源对象和目标对象可能是不同类,但字段名和类型相同
  • 字段可能是 private 修饰(需要突破封装)
  • 可能需要忽略某些字段(如 serialVersionUID、密码字段)
  • 基本类型与包装类型需要兼容(如 int 和 Integer)

2. 实现思路:反射如何完成复制?

核心步骤是动态获取字段→突破访问限制→读取源值→写入目标对象,流程如下:

  1. 获取源对象和目标对象的 Class 对象
  2. 遍历源对象的所有字段(包括私有字段,用getDeclaredFields()
  3. 对每个字段:
    • 检查是否需要忽略(如在忽略列表中则跳过)
    • 调用setAccessible(true)解除访问限制
    • 在目标对象中查找同名且类型兼容的字段
    • 从源对象获取字段值,写入目标对象的对应字段

3. 代码实现:通用属性复制工具类

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;/*** 基于反射的对象属性复制工具*/
public class ReflectBeanCopier {/*** 复制源对象属性到目标对象* @param source 源对象(非null)* @param target 目标对象(非null)* @param ignoreFields 需要忽略的字段名*/public static void copyProperties(Object source, Object target, String... ignoreFields) {if (source == null || target == null) {throw new IllegalArgumentException("源对象和目标对象不能为null");}// 转换忽略字段为Set,方便判断Set<String> ignoreSet = new HashSet<>();for (String field : ignoreFields) {ignoreSet.add(field);}// 获取源对象和目标对象的ClassClass<?> sourceClass = source.getClass();Class<?> targetClass = target.getClass();// 遍历源对象的所有字段(包括私有)Field[] sourceFields = sourceClass.getDeclaredFields();for (Field sourceField : sourceFields) {String fieldName = sourceField.getName();// 跳过忽略的字段if (ignoreSet.contains(fieldName)) {continue;}try {// 解除源字段的访问限制sourceField.setAccessible(true);// 在目标对象中查找同名字段Field targetField;try {targetField = targetClass.getDeclaredField(fieldName);} catch (NoSuchFieldException e) {// 目标对象没有该字段,跳过continue;}// 检查字段类型是否兼容(基本类型与包装类型需特殊处理)if (!isTypeCompatible(sourceField.getType(), targetField.getType())) {continue; // 类型不兼容,跳过}// 解除目标字段的访问限制targetField.setAccessible(true);// 读取源字段值,写入目标字段Object value = sourceField.get(source);targetField.set(target, value);} catch (IllegalAccessException e) {// 理论上不会触发,因为已设置setAccessible(true)throw new RuntimeException("复制字段[" + fieldName + "]失败", e);}}}/*** 检查源类型和目标类型是否兼容*/private static boolean isTypeCompatible(Class<?> sourceType, Class<?> targetType) {// 类型相同直接兼容if (sourceType.equals(targetType)) {return true;}// 基本类型与包装类型兼容(如int和Integer)if (sourceType.isPrimitive()) {return wrapPrimitive(sourceType).equals(targetType);}if (targetType.isPrimitive()) {return wrapPrimitive(targetType).equals(sourceType);}// 其他情况:判断目标类型是否是源类型的父类/接口return targetType.isAssignableFrom(sourceType);}/*** 将基本类型转换为对应的包装类型*/private static Class<?> wrapPrimitive(Class<?> primitiveType) {if (primitiveType == int.class) return Integer.class;if (primitiveType == boolean.class) return Boolean.class;if (primitiveType == long.class) return Long.class;if (primitiveType == double.class) return Double.class;if (primitiveType == float.class) return Float.class;if (primitiveType == short.class) return Short.class;if (primitiveType == byte.class) return Byte.class;if (primitiveType == char.class) return Character.class;return primitiveType; // 非基本类型直接返回}
}

4. 使用示例:复制 UserDTO 到 UserPO

// 定义源对象类(DTO)
class UserDTO {private String username;private int age;private String password; // 敏感字段,需要忽略// 构造器、getter、setter省略
}// 定义目标对象类(PO)
class UserPO {private String username;private Integer age; // 与DTO的int类型兼容private String password; // 敏感字段,需要忽略private String createTime; // DTO中没有,不影响// 构造器、getter、setter、toString省略
}// 测试复制功能
public class CopyTest {public static void main(String[] args) {UserDTO dto = new UserDTO();dto.setUsername("梵得儿shi");dto.setAge(30);dto.setPassword("123456"); // 敏感字段UserPO po = new UserPO();// 复制时忽略password字段ReflectBeanCopier.copyProperties(dto, po, "password");System.out.println(po); // 输出:UserPO{username='梵得儿shi', age=30, password='null', createTime='null'}// 可见username和age被正确复制,password被忽略}
}

5. 反射属性复制流程图解

下图展示了反射复制属性的核心步骤,红色箭头代表反射突破封装的过程:

图中清晰展示了反射的核心作用:通过getDeclaredFields()获取私有字段,用setAccessible(true)突破封装,最终完成属性复制;同时敏感字段(如 password)被忽略,体现了工具的灵活性。

6. 注意事项:避坑指南

  • 性能优化:频繁调用时,缓存Field对象(反射获取字段的开销远大于复制本身)。
  • 继承字段:默认getDeclaredFields()只获取当前类的字段,如需复制父类字段,需递归遍历父类Class(注意过滤Object类)。
  • 特殊类型处理:对于集合、数组等复杂类型,默认是浅拷贝,如需深拷贝需额外处理。
  • 安全性:避免复制不可变对象(如Stringvalue字段),可能导致不可预期的后果。

二、实战二:反射调用私有方法

有时我们会遇到这样的场景:第三方库的某个类有个私有方法正好满足需求,但没有提供 public 接口;或者单元测试需要覆盖私有方法。这时反射就能绕过访问限制,直接调用私有方法。

1. 需求分析:调用私有方法需要什么?

私有方法的调用比属性复制更简单,核心是找到方法→解除访问限制→传入参数调用,但需要注意:

  • 方法名必须准确
  • 参数类型必须严格匹配(基本类型与包装类型不兼容,如intInteger是不同的参数类型)
  • 静态私有方法和实例私有方法的调用方式不同(静态方法 invoke 时第一个参数为 null)

2. 实现思路:反射调用私有方法的步骤

  1. 获取目标类的Class对象
  2. getDeclaredMethod(String name, Class<?>... parameterTypes)获取私有方法(getMethod()只能获取 public 方法)
  3. 调用method.setAccessible(true)解除访问限制
  4. 调用method.invoke(Object obj, Object... args)执行方法(obj 为实例对象,静态方法传 null)

3. 代码实现:调用私有方法的工具类

import java.lang.reflect.Method;/*** 基于反射的私有方法调用工具*/
public class ReflectPrivateMethodInvoker {/*** 调用实例对象的私有方法* @param obj 实例对象* @param methodName 方法名* @param parameterTypes 参数类型数组(如new Class[]{String.class, int.class})* @param args 方法参数* @return 方法返回值*/public static Object invokeInstanceMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args) {if (obj == null || methodName == null) {throw new IllegalArgumentException("对象和方法名不能为null");}try {// 获取方法(包括私有)Method method = obj.getClass().getDeclaredMethod(methodName, parameterTypes);// 解除访问限制method.setAccessible(true);// 调用方法return method.invoke(obj, args);} catch (Exception e) {throw new RuntimeException("调用私有方法[" + methodName + "]失败", e);}}/*** 调用静态私有方法* @param clazz 目标类* @param methodName 方法名* @param parameterTypes 参数类型数组* @param args 方法参数* @return 方法返回值*/public static Object invokeStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) {if (clazz == null || methodName == null) {throw new IllegalArgumentException("类和方法名不能为null");}try {Method method = clazz.getDeclaredMethod(methodName, parameterTypes);method.setAccessible(true);// 静态方法invoke的第一个参数为nullreturn method.invoke(null, args);} catch (Exception e) {throw new RuntimeException("调用静态私有方法[" + methodName + "]失败", e);}}
}

4. 使用示例:调用私有方法和静态私有方法

class PrivateMethodDemo {// 实例私有方法private String encrypt(String content, int key) {// 简单加密逻辑:每个字符ASCII码加keyStringBuilder sb = new StringBuilder();for (char c : content.toCharArray()) {sb.append((char) (c + key));}return sb.toString();}// 静态私有方法private static boolean isEmailValid(String email) {return email != null && email.contains("@");}
}// 测试调用私有方法
public class PrivateMethodTest {public static void main(String[] args) {PrivateMethodDemo demo = new PrivateMethodDemo();// 调用实例私有方法encrypt// 参数类型:String和int(注意必须用int.class,不能用Integer.class)Class<?>[] paramTypes = new Class[]{String.class, int.class};Object result = ReflectPrivateMethodInvoker.invokeInstanceMethod(demo, "encrypt", paramTypes, "hello", 3);System.out.println("加密结果:" + result); // 输出:khoor(h+3=k, e+3=h, 以此类推)// 调用静态私有方法isEmailValidClass<?>[] staticParamTypes = new Class[]{String.class};Object valid = ReflectPrivateMethodInvoker.invokeStaticMethod(PrivateMethodDemo.class, "isEmailValid", staticParamTypes, "test@example.com");System.out.println("邮箱是否有效:" + valid); // 输出:true}
}

5. 反射调用私有方法流程图解

下图展示了反射突破方法访问权限的过程,用 “锁” 和 “钥匙” 形象比喻封装与反射的关系:

图中私有方法被 “锁”(private 修饰)保护,反射通过getDeclaredMethod()找到方法,用setAccessible(true)(钥匙)打开锁,最终通过invoke()调用方法,直观展示了反射突破封装的过程。

6. 注意事项:风险与限制

  • 参数类型严格匹配getDeclaredMethodparameterTypes必须与方法定义完全一致。例如方法参数是int,传入Integer.class会抛出NoSuchMethodException(需用int.class)。
  • 版本兼容性风险:私有方法属于类的内部实现,第三方库升级时可能被删除或修改,依赖反射调用会导致兼容性问题。
  • 安全性问题:滥用setAccessible(true)会破坏封装性,可能导致恶意代码调用危险方法(如System.exit()),需严格控制使用范围。
  • 异常处理:反射调用可能抛出IllegalAccessException(未解除访问限制)、InvocationTargetException(方法内部抛出异常)等,需妥善处理。

三、总结:反射实战的核心启示

反射的这两个实战案例,本质上都是通过动态获取类元数据,突破编译期的访问限制,实现通用化或特殊化的功能。但同时也需牢记:

  • 反射是 “非常规手段”:优先使用 public API,只有在必须通用化(如属性复制工具)或无其他选择(如调用私有方法)时才用反射。
  • 性能与安全需权衡:反射的灵活性伴随性能损耗和安全风险,使用时需做好缓存优化和权限控制。
  • 理解底层原理:掌握getDeclaredFields()getFields()的区别、setAccessible()的作用、参数类型匹配规则等细节,才能避免踩坑。

反射就像一把精密的螺丝刀,能拧下常规工具够不到的螺丝,但如果用错地方,也可能损坏机器。希望通过这两个实战案例,你能真正理解反射的 “可为” 与 “不可为”,让它成为开发中的得力助手。

觉得文章对你有帮助?点个赞👍支持一下!有疑问欢迎在评论区讨论~

http://www.dtcms.com/a/506943.html

相关文章:

  • 火星时代UE奶瓜粒子特效②
  • 网站制作流程图wordpress 维文版
  • MySQL与K8s:数据库运维新范式
  • 第9篇 opencv提取矩形角度不是很准确的处理方法
  • 检测十字标 opencv python
  • NSSCTF - Web | 【SWPUCTF 2021 新生赛】Do_you_know_http
  • Linux小课堂: 流、重定向与 cut 命令进阶
  • 虚拟内存核心常识
  • ubuntu配置mysql8.0并设置Navicat网络连接
  • 深圳网站维护一般多少钱网址大全黄免费片
  • 从若依框架看权限设计与数据字典:背后的工程化思考
  • 邦策网站建设平台网站建设文化咨询
  • ASTM D7033-2022 定向刨花板检测
  • 使用ThreadLocal的一些注意事项
  • Kotion 常见用法注意事项(持续更新...)
  • 如何使用思维导图提升信息整理效率
  • K-VXE-TABLE二次封装,含table‘自定义列功能
  • 基于 GEE 开发的一种利用 OTSU 算法实现水体提取的便捷工具
  • Linux小课堂: 深入解析 top、htop、glances 及进程终止机制
  • 建设协会网站洛阳伟创科技
  • MongoDB 提供的 `GridFSTemplate` 操作 GridFS 大文件系统的常用查询方式
  • 2025年ASOC SCI2区TOP,基于模糊分组的多仓库多无人机电力杆巡检模因算法,深度解析+性能实测
  • 无人机地面站中不同的飞行模式具体含义释义(开源飞控常用的5种模式)
  • Inventor 转换为 3DXML 全流程技术指南:附迪威模型网在线方案
  • Maven POM 简介
  • pytorch踩坑记录
  • seo每天一贴博客南宁网站排名优化电话
  • 手机端网站开发书籍徐州vi设计公司
  • STM32F1和STM32F4在配置硬件SPI1时有什么不同?
  • 衣柜灯橱柜灯MCU方案开发