• Java 对象深拷贝工具类


    目录

    1. 使用场景

    1.1 场景一

    1.2 场景二

    2. Spring 中的对象拷贝

    3. 本工具类中的对象拷贝

    3.1 拷贝对象本身(单个)

    3.2 拷贝对象本身(批量)

    3.3 拷贝对象属性至其他类(单个)

    3.4 拷贝对象属性至其他类(批量)

    4. 工具类源码


    1. 使用场景

    我们在Java编码中,有时候可能会经常遇到对象拷贝的场景。

    1.1 场景一

    当我们更新一个对象的时候,如果要记录对象属性的前后变化,那么在更新对象之前,我们应该首先将对象拷贝暂存起来,且这个时候的拷贝一定是深拷贝(内存地址不同的两个对象),因为Java存在对象引用,将一个对象赋值给另外一个对象,他是浅拷贝的(两个不同变量名,但实际内存地址一样的两个对象)的话,也就是说当我们去更新完成属性值的时候,其实是设置的同一个对象,那么这个时候就会导致更新前后无变化的情况。

    1.2 场景二

    又比如,当我们从数据库中查询出一个实体对象的时候,这个对象往往对应的是和数据库字段 一 一 对应的实体,但这个实体往往又不会满足我们的页面需求。比如我们查询学生课程表的时候,我们数据库往往只是存的一个 id 对应关系,但页面往往是展示的名称,那么这个名称字段我们的表对应的实体类是不应该存在的,这个时候,我们应该创建一个对应的 VO (View Object)类,把额外的需要字段定义在这里面,同时可以去继承原表实体类,这样的一个对象就满足了。到时候,我们把原表实体对应的字段值拷贝到 VO 对象中后,再设置其他表额外的字段,这样就可以返回给前端页面进行展示了。

    综上:所以,对象拷贝还是挺有用途的,但如果我们拷贝对象的时候,去一个一个字段挨着进行取值拷贝的话,难免代码看上去不够优雅。于是,搞一个对象拷贝工具类还是很有必要的。

    2. Spring 中的对象拷贝

    其实,在 Spring 中,也有类似的拷贝方法。他就是位于 org.springframework.beans.BeanUtils 工具类中的 copyProperties 方法。下面就简单演示下这个方法的使用效果。

    为了方便演示,我们创建两个有部分相同属性的对象 Cat 类和 Dog 类(都有 name 和 age 字段)。

    Cat 类如下:

    1. @Data
    2. public class Cat {
    3. private String name;
    4. private Integer age;
    5. private String color;
    6. }

    Dog 类如下:

    1. @Data
    2. public class Dog {
    3. private String name;
    4. private Integer age;
    5. private String address;
    6. }

    测试代码:

    1. import org.springframework.beans.BeanUtils;
    2. public class Test {
    3. public static void main(String[] args) {
    4. // 实例化一个 Cat 对象并赋值属性
    5. Cat cat = new Cat();
    6. cat.setName("tom");
    7. cat.setAge(5);
    8. // 实例化一个 Dog 对象
    9. Dog dog = new Dog();
    10. // 将 cat 对象中的属性值拷贝至 dog 中
    11. BeanUtils.copyProperties(cat, dog);
    12. System.out.println("拷贝后:" + dog);
    13. }
    14. }

     测试效果:

    可以看到,相同的 name 和 age 已经复制过去了。

    3. 本工具类中的对象拷贝

    上面我们演示了 Spring 下 BeanUtils 工具类中的对象属性拷贝,虽然他也可以成功拷贝对象中的属性,但对于我个人来说,还是有点不适应。

    首先,Spring 去拷贝一个对象属性的时候,需要先创建好另外一个对象,然后再进行属性拷贝,这一步对象创建是明显可以放到工具方法中去的。

    其次,如果只是本类复制的话,参数只需要传一个源对象的实例就应该够了,而Spring就算拷贝本类,也得传两个参数,即源实例对象和目标实例对象。

    另外,Spring 的对象拷贝不支持批量拷贝,比如我们将 List 属性拷贝后,生成一个 List 中,只能自己循环去拷贝生成每个 Dog,然后添加到 List 中。

    于是,敝人针对个人习惯,编写了一个适合自己的编码习惯的对象拷贝工具类 BeanUtils(类名还是参照的 Spring),具体使用效果如下。

    下面先做效果演示,工具类源码放在文章最后。

    3.1 拷贝对象本身(单个)

    比如,我们想复制一个对象本身(如 cat),那么直接使用下面这个方法就可以了。

    Cat newCat = BeanUtils.copy(cat);

    测试代码:

     测试效果:

    从测试结果我们可以看到,源对象和复制对象的每个字段值已经拷贝过去了,但两个对象的内存 hashCode 并不相同,说明并不是同一个对象,也就说我们是进行深拷贝的,两个对象是互不影响的。

    另外,我们这个工具类不但支持类对象本身属性拷贝,连父类属性拷贝也是支持的。

    比如,Cat类去继承下面这个 Animal 类:

    1. @Data
    2. public class Animal {
    3. private Integer price;
    4. private Date birth;
    5. }
    1. @Data
    2. public class Cat extends Animal {
    3. private String name;
    4. private Integer age;
    5. private String color;
    6. }

    我们再试试测试一下:

    测试效果:

    可以看到,我们的父类属性字段值也确实复制成功了。

    3.2 拷贝对象本身(批量)

    工具类中不仅支对单个对象拷贝的,对多个对象的拷贝也是支持的。

    List newCatList = BeanUtils.copyList(catList);

    测试代码:

    测试效果:

    可以看到,批量属性复制也是OK的,拷贝后的集合中每个对象新生成的深拷贝对象。

    3.3 拷贝对象属性至其他类(单个)

    上面,我们演示了对象本身复制的效果,下面继续演示下拷贝同名字段到其他属性的效果。

    Dog dog = BeanUtils.copy(cat, Dog.class);

    我们把 Cat 中的同名字段属性拷贝到 Dog 中去,我们让 Dog 也去继承下 Anima 类。

    1. @Data
    2. public class Dog extends Animal {
    3. private String name;
    4. private Integer age;
    5. private String address;
    6. }

    测试代码:

    因为拷贝前后是两个完全不一样的对象了,所以这里就不再打印地址 hashCode 来进行说明是深拷贝了。

    测试效果:

    可以看到 cat 中的所有相同属性已经拷贝到 dog 中去了。

    3.4 拷贝对象属性至其他类(批量)

    同理,我们拷贝对象属性至其他类也是支持批量操作的。

    List dogs = BeanUtils.copyList(cats, Dog.calss);

    测试代码:

    测试效果:

    可以看到,批量复制也是OK的。

    至此,整个对象的拷贝的四个常用方法已经都已经支持了。

    4. 工具类源码

    下面就是整个工具类的源码 BeanUtils :

    1. package com.zyq.utils.common;
    2. import java.lang.reflect.Field;
    3. import java.util.*;
    4. /**
    5. * @author zyqok
    6. * @since 2022/07/18
    7. */
    8. @SuppressWarnings("unused")
    9. public class BeanUtils {
    10. /**
    11. * 拷贝数据到新对象(单个)
    12. *
    13. * @param source 源实例对象
    14. * @return 拷贝后的新实例对象
    15. */
    16. public static T copy(T source) {
    17. if (Objects.isNull(source)) {
    18. return null;
    19. }
    20. Class c = source.getClass();
    21. List fields = getFields(c);
    22. return newInstance(source, c, fields);
    23. }
    24. /**
    25. * 拷贝数据到新对象(批量)
    26. *
    27. * @param sourceList 源实例对象集合
    28. * @return 拷贝后的新实例对象集合
    29. */
    30. public static List copyList(List sourceList) {
    31. if (Objects.isNull(sourceList) || sourceList.isEmpty()) {
    32. return Collections.emptyList();
    33. }
    34. Class c = getClass(sourceList);
    35. if (Objects.isNull(c)) {
    36. return Collections.emptyList();
    37. }
    38. List fields = getFields(c);
    39. List ts = new ArrayList<>();
    40. for (T t : sourceList) {
    41. T s = newInstance(t, c, fields);
    42. if (Objects.nonNull(s)) {
    43. ts.add(s);
    44. }
    45. }
    46. return ts;
    47. }
    48. /**
    49. * 单个深度拷贝
    50. *
    51. * @param source 源实例化对象
    52. * @param target 目标对象类(如:User.class)
    53. * @return 目标实例化对象
    54. */
    55. public static T copy(Object source, Class target) {
    56. if (Objects.isNull(source) || Objects.isNull(target)) {
    57. return null;
    58. }
    59. List sourceFields = getFields(source.getClass());
    60. List targetFields = getFields(target);
    61. T t = null;
    62. try {
    63. t = newInstance(source, target, sourceFields, targetFields);
    64. } catch (Exception e) {
    65. e.printStackTrace();
    66. }
    67. return t;
    68. }
    69. /**
    70. * 批量深度拷贝(如果原集合中有null,则自动忽略)
    71. *
    72. * @param sourceList 源实例化对象集合
    73. * @param target 目标对象类(如:User.class)
    74. * @return 目标实例化对象集合
    75. */
    76. public static List copyList(List sourceList, Class target) {
    77. if (Objects.isNull(sourceList) || sourceList.isEmpty() || Objects.isNull(target)) {
    78. return Collections.emptyList();
    79. }
    80. Class c = getClass(sourceList);
    81. if (Objects.isNull(c)) {
    82. return Collections.emptyList();
    83. }
    84. List sourceFields = getFields(c);
    85. List targetFields = getFields(target);
    86. List ks = new ArrayList<>();
    87. for (T t : sourceList) {
    88. if (Objects.nonNull(t)) {
    89. try {
    90. K k = newInstance(t, target, sourceFields, targetFields);
    91. ks.add(k);
    92. } catch (Exception e) {
    93. e.printStackTrace();
    94. }
    95. }
    96. }
    97. return ks;
    98. }
    99. /**
    100. * 获取List集合中的类名
    101. *
    102. * @param list 对象集合
    103. * @return 类名
    104. */
    105. private static Class getClass(List list) {
    106. for (T t : list) {
    107. if (Objects.nonNull(t)) {
    108. return t.getClass();
    109. }
    110. }
    111. return null;
    112. }
    113. /**
    114. * 实例化同源对象
    115. *
    116. * @param source 源对象
    117. * @param c 源对象类名
    118. * @param fields 源对象属性集合
    119. * @return 同源新对象
    120. */
    121. @SuppressWarnings("unchecked")
    122. private static T newInstance(T source, Class c, List fields) {
    123. T t = null;
    124. try {
    125. t = (T) c.newInstance();
    126. for (Field field : fields) {
    127. field.setAccessible(true);
    128. field.set(t, field.get(source));
    129. }
    130. } catch (Exception e) {
    131. e.printStackTrace();
    132. }
    133. return t;
    134. }
    135. /**
    136. * 目标实例化对象
    137. *
    138. * @param source 原对实例化象
    139. * @param target 目标对象类
    140. * @param sourceFields 源对象字段集合
    141. * @param targetFields 目标对象属性字段集合
    142. * @return 目标实例化对象
    143. */
    144. private static T newInstance(Object source, Class target, List sourceFields,
    145. List targetFields) throws Exception {
    146. T t = target.newInstance();
    147. if (targetFields.isEmpty()) {
    148. return t;
    149. }
    150. for (Field field : sourceFields) {
    151. field.setAccessible(true);
    152. Object o = field.get(source);
    153. Field sameField = getSameField(field, targetFields);
    154. if (Objects.nonNull(sameField)) {
    155. sameField.setAccessible(true);
    156. sameField.set(t, o);
    157. }
    158. }
    159. return t;
    160. }
    161. /**
    162. * 获取目标对象中同源对象属性相同的属性(字段名称,字段类型一致则判定为相同)
    163. *
    164. * @param field 源对象属性
    165. * @param fields 目标对象属性集合
    166. * @return 目标对象相同的属性
    167. */
    168. private static Field getSameField(Field field, List fields) {
    169. String name = field.getName();
    170. String type = field.getType().getName();
    171. for (Field f : fields) {
    172. if (name.equals(f.getName()) && type.equals(f.getType().getName())) {
    173. return f;
    174. }
    175. }
    176. return null;
    177. }
    178. /**
    179. * 获取一个类中的所有属性(包括父类属性)
    180. *
    181. * @param c 类名
    182. * @return List
    183. */
    184. private static List getFields(Class c) {
    185. List fieldList = new ArrayList<>();
    186. Field[] fields = c.getDeclaredFields();
    187. if (fields.length > 0) {
    188. fieldList.addAll(Arrays.asList(fields));
    189. }
    190. return getSuperClassFields(c, fieldList);
    191. }
    192. /**
    193. * 递归获取父类属性
    194. *
    195. * @param o 类名
    196. * @param allFields 外层定义的所有属性集合
    197. * @return 父类所有属性
    198. */
    199. private static List getSuperClassFields(Class o, List allFields) {
    200. Class superclass = o.getSuperclass();
    201. if (Objects.isNull(superclass) || Object.class.getName().equals(superclass.getName())) {
    202. return allFields;
    203. }
    204. Field[] fields = superclass.getDeclaredFields();
    205. if (fields.length == 0) {
    206. return allFields;
    207. }
    208. allFields.addAll(Arrays.asList(fields));
    209. return getSuperClassFields(superclass, allFields);
    210. }
    211. }

  • 相关阅读:
    图解神经网络的基本原理,神经网络基本结构说明
    bugxxx
    Dockerfile自定义镜像实操【镜像结构、Dockerfile语法、构建Java项目】
    MFC Windows 程序设计[151]之色彩控件之谜
    一个Vue3数字框区间指令及其衍生的一个知识点
    指针相关面试题目
    C/C++苹果和虫子 2019年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析
    1870. 准时到达的列车最小时速-二分法
    景联文科技:数据供应商在新一轮AI热潮中的重要性
    EchoServer回显服务器简单测试
  • 原文地址:https://blog.csdn.net/sunnyzyq/article/details/125856311