Java中如何克隆集合——ArrayList和HashSet深拷贝

时间:2021-08-04 19:53:21

  编程人员经常误用各个集合类提供的拷贝构造函数作为克隆ListSetArrayListHashSet或者其他集合实现的方法。需要记住的是,Java集合的拷贝构造函数只提供浅拷贝而不是深拷贝,这意味着存储在原始List和克隆List中的对象是相同的,指向Java堆内存中相同的位置。增加了这个误解的原因之一是对于不可变对象集合的浅克隆。由于不可变性,即使两个集合指向相同的对象是可以的。字符串池包含的字符串就是这种情况,更改一个不会影响到另一个。使用ArrayList的拷贝构造函数创建雇员List的拷贝时就会出现问题,Employee类不是不可变的。在这种情况下,如果原始集合修改了雇员信息,这个变化也将反映到克隆集合。同样如果克隆集合雇员信息发生变化,原始集合也会被更改。绝大多数情况下,这种变化不是我们所希望的,克隆对象应该与原始对象独立。解决这个问题的方法是深克隆集合,深克隆将递归克隆对象直到基本数据类型或者不可变类。本文将了解一下深拷贝ArrayList或者HashSet等集合类的一种方法。如果你了解深拷贝与浅拷贝之间的区别,那么理解集合深克隆的方法就会很简单。

Java集合的深克隆

下面例子有一个Employee集合,Employee是可变对象,成员变量namedesignation。它们存储在HashSet中。使用java.util.Collection接口的addAll()方法创建集合拷贝。然后修改存储在原始集合每个Employee对象的designation值。理想情况下这个改变不会影响克隆集合,因为克隆集合和原始集合应该相互独立,但是克隆集合也被改变了。修正这个问题的方法是对存储在Collection类中的元素深克隆。

 1 /**
2 *
3 * @ClassName: CollectionCloningTest
4 * TODO
5 * @author xingle
6 * @date 2015-3-20 下午3:32:22
7 */
8 public class CollectionCloningTest {
9
10 public static void main(String[] args){
11 ArrayList<Employee> org = new ArrayList<Employee>();
12 org.add(new Employee("Joe", "Manager"));
13 org.add(new Employee("Tim", "Developer"));
14 org.add(new Employee("Frank", "Developer"));
15
16 Collection<Employee> copy = new HashSet<>(org);
17
18 System.out.println("原来的集合: "+org);
19 System.out.println("复制的集合: "+copy);
20
21 Iterator<Employee> orgItr = org.iterator();
22 while(orgItr.hasNext()){
23 orgItr.next().setDesignation("staff");
24
25 }
26
27 System.out.println("修改后原来的集合: "+org);
28 System.out.println("修改后复制的集合: "+copy);
29 }
30
31 }
32
33
34 class Employee {
35 private String name;
36 private String designation;
37
38 public Employee(String name, String designation) {
39 this.name = name;
40 this.designation = designation;
41 }
42
43 public String getDesignation() {
44 return designation;
45 }
46
47 public void setDesignation(String designation) {
48 this.designation = designation;
49 }
50
51 public String getName() {
52 return name;
53 }
54
55 public void setName(String name) {
56 this.name = name;
57 }
58
59 @Override
60 public String toString() {
61 return String.format("%s: %s", name, designation );
62 }
63
64 }

执行结果:

Java中如何克隆集合——ArrayList和HashSet深拷贝

 

可以看到改变原始CollectionEmployee对象(改变designation为”staff“)在克隆集合中也有所反映,因为克隆是浅拷贝,指向堆中相同的Employee对象。为了修正这个问题,需要遍历集合,深克隆Employee对象,在这之前,要重写Employee对象的clone方法。

1)Employee实现Cloneable接口
2)为Employee类增加下面的clone()方法

3)不使用拷贝构造函数,使用下面的代码来深拷贝集合

 1 public class CollectionCloningTest {
2
3 public static void main(String[] args){
4 ArrayList<Employee> org = new ArrayList<Employee>();
5 org.add(new Employee("Joe", "Manager"));
6 org.add(new Employee("Tim", "Developer"));
7 org.add(new Employee("Frank", "Developer"));
8
9 //Collection<Employee> copy = new HashSet<>(org);
10 Collection<Employee> copy = new HashSet<Employee>(org.size());
11
12
13 System.out.println("原来的集合: "+org);
14 System.out.println("复制的集合: "+copy);
15
16 Iterator<Employee> orgItr = org.iterator();
17 while(orgItr.hasNext()){
18 //orgItr.next().setDesignation("staff");
19 copy.add(orgItr.next().clone());
20
21 }
22
23
24 Iterator<Employee> orgItr2 = org.iterator();
25 while(orgItr2.hasNext()){
26 orgItr2.next().setDesignation("staff");
27 }
28 System.out.println("修改后原来的集合: "+org);
29 System.out.println("修改后复制的集合: "+copy);
30 }
31
32 }
33
34
35 class Employee implements Cloneable{
36 private String name;
37 private String designation;
38
39 public Employee(String name, String designation) {
40 this.name = name;
41 this.designation = designation;
42 }
43
44 public String getDesignation() {
45 return designation;
46 }
47
48 public void setDesignation(String designation) {
49 this.designation = designation;
50 }
51
52 public String getName() {
53 return name;
54 }
55
56 public void setName(String name) {
57 this.name = name;
58 }
59
60 @Override
61 public String toString() {
62 return String.format("%s: %s", name, designation );
63 }
64
65 @Override
66 protected Employee clone(){
67 try {
68 Employee result = (Employee) super.clone();
69 return result;
70 } catch (CloneNotSupportedException e) {
71 throw new RuntimeException(e); // won't happen
72 }
73
74 }
75 }

 

执行结果:

Java中如何克隆集合——ArrayList和HashSet深拷贝

可以看到克隆集合和原始集合相互独立,它们指向不同的对象。
Java中如何克隆集合——ArrayList和HashSet深拷贝

这就是Java中如何克隆集合的内容。现在我们知道拷贝构造函数或者ListSet等各种集合类的addAll()方法仅仅创建了集合的浅拷贝,而且原始集合和克隆集合指向相同的对象。为避免这个问题,应该深克隆集合,遍历集合克隆每个元素。尽管这要求集合中的对象必须支持深克隆操作。