0


Java 对象深拷贝工具类

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<Cat> 属性拷贝后,生成一个 List<Dog> 中,只能自己循环去拷贝生成每个 Dog,然后添加到 List<Dog> 中。

于是,敝人针对个人习惯,编写了一个适合自己的编码习惯的对象拷贝工具类 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<Cat> 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<Dog> 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> T copy(T source) {
  17. if (Objects.isNull(source)) {
  18. return null;
  19. }
  20. Class<?> c = source.getClass();
  21. List<Field> fields = getFields(c);
  22. return newInstance(source, c, fields);
  23. }
  24. /**
  25. * 拷贝数据到新对象(批量)
  26. *
  27. * @param sourceList 源实例对象集合
  28. * @return 拷贝后的新实例对象集合
  29. */
  30. public static <T> List<T> copyList(List<T> 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<Field> fields = getFields(c);
  39. List<T> 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> T copy(Object source, Class<T> target) {
  56. if (Objects.isNull(source) || Objects.isNull(target)) {
  57. return null;
  58. }
  59. List<Field> sourceFields = getFields(source.getClass());
  60. List<Field> 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 <T, K> List<K> copyList(List<T> sourceList, Class<K> 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<Field> sourceFields = getFields(c);
  85. List<Field> targetFields = getFields(target);
  86. List<K> 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 <T> Class<?> getClass(List<T> 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> T newInstance(T source, Class<?> c, List<Field> 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> T newInstance(Object source, Class<T> target, List<Field> sourceFields,
  145. List<Field> 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<Field> 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<Field>
  183. */
  184. private static List<Field> getFields(Class<?> c) {
  185. List<Field> 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<Field> getSuperClassFields(Class<?> o, List<Field> 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. }
标签: java jvm 开发语言

本文转载自: https://blog.csdn.net/sunnyzyq/article/details/125856311
版权归原作者 zyqok 所有, 如有侵权,请联系我们删除。

“Java 对象深拷贝工具类”的评论:

还没有评论