zoukankan      html  css  js  c++  java
  • Java中如何克隆集合——ArrayList和HashSet深拷贝

      编程人员经常误用各个集合类提供的拷贝构造函数作为克隆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 }

    执行结果:

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

  • 相关阅读:
    5.2 spring5源码--spring AOP源码分析三---切面源码分析
    5.2 spring5源码--spring AOP源码分析二--切面的配置方式
    在Dubbo中使用Zookeeper入门案例
    Dubbo直连方式改造
    Dubbo直连方式
    16.3.3 对矢量可执行的其它操作
    16.3.2 可对矢量(vector)执行的操作
    16.3 标准模板库
    16.2.2 有关智能指针的注意事项
    16.2.1 使用智能指针
  • 原文地址:https://www.cnblogs.com/xingele0917/p/4354013.html
Copyright © 2011-2022 走看看