无论是浅复制还是深复制,对于基本类型和 String 来说都是没有影响的,只对引用类型有影响。
浅复制:对基本数据类型进行值传递,对引用数据类型进行引用传递。如果复制的是引用类型,那么目标对象复制的只是源对象的地址,无论目标对象还是源对象改变,他们都会一起改变。
深复制:对基本数据类型进行值传递,对引用数据类型创建一个新的对象,并复制其内容。复制完之后两者是完全隔开的,没有任何关系,这时无论操作源对象还是目标对象都对另一个没有影响。
下面以复制 List 集合为例,介绍一些浅复制和深复制的方法。
1 浅复制方法
直接使用下列方法均无法对 List 集合实现深复制。如果源对象不是像 List 集合这样的复杂对象,那么可以根据实际场景选择使用 clone() 或 BeanUtils.copyProperties()。
1.1 clone()
默认是浅复制,若想实现深复制,则需要重写 clone() 方法(设计模式原型模式例子中有写过)。
1.2 BeanUtils.copyProperties()
可用来将两个字段相同的对象进行属性值的复制,如果两个对象之间存在不相同的属性,则需要手动处理。另外要注意,BeanUtils.copyProperties() 虽写法简单,但与传统的 get / set 方式相比,存在性能问题,花费的时间成本很高;而且它在两个不同的包下面都有,传递的参数还是相反的:
- org.springframework.beans.BeanUtils.copyProperties(a, b):a 复制到 b;
- org.apache.commons.beanutils.BeanUtils.copyProperties(a, b):b 复制到 a,性能较差。
1.3 for 循环遍历元素添加到目标集合
这个方法跟前两种方法不同的地方在于,目标集合对象是 new 出来的,所以它跟源集合是不相等的,如果对源集合进行 add()、remove() 操作,不会影响到目标集合。
1.4 构造方法 new ArrayList<>()
跟用 for 循环的效果一样。
1.5 Collections.copy()
跟用 for 循环的效果一样。
1.6 list.addAll()
跟用 for 循环的效果一样。
1.7 System.arraycopy()
跟用 for 循环的效果一样。
只有在某些特定情况下,上面某些浅复制的做法才能达到跟深复制一样的效果。比如对于 List<String> 这样的对象来说就是可以的,因为 String 相比于实际项目中的自定义实体类来说,它没有给外部提供可以修改数据的 setter 方法。因此,只有确保 List<T> 中的 T 类对象不会被外部修改和破坏的情况下,才能选用上述做法(基本类型和 String)。
2 深复制方法
一个比较靠谱的实现深复制 List 的方法是:将集合数据 List<T> 转换为 JSON 字符串,然后再把 JSON 字符串转换回 List<T>,中间通过 Gson 来实现。Gson 由 Google 内部自行研发而来,是目前功能最全的 JSON 解析神器,几乎能覆盖所有情况。但如果有性能上的要求,则可以结合阿里巴巴的 FastJson 一起使用,即:将对象转换为 JSON 的过程用 Gson,确保数据的正确;将 JSON 转换为对象的过程用 FastJson,提升性能。
代码示例:(复制简单的自定义对象时也可参考此方法)
1 | import com.google.gson.Gson; |
执行结果:
1 | before list1: [user1, user2, user3] [org1, org2, org3] |
这样得到的集合 list2 就是源集合 list1 的深复制了。观察上面的执行结果可以发现,在修改源集合 list1 中的数据后,并没有影响到 list2 的数据。