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

Java集合的深克隆

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

 /**
*
* @ClassName: CollectionCloningTest
* TODO
* @author xingle
* @date 2015-3-20 下午3:32:22
*/
public class CollectionCloningTest { public static void main(String[] args){
ArrayList<Employee> org = new ArrayList<Employee>();
org.add(new Employee("Joe", "Manager"));
org.add(new Employee("Tim", "Developer"));
org.add(new Employee("Frank", "Developer")); Collection<Employee> copy = new HashSet<>(org); System.out.println("原来的集合: "+org);
System.out.println("复制的集合: "+copy); Iterator<Employee> orgItr = org.iterator();
while(orgItr.hasNext()){
orgItr.next().setDesignation("staff"); } System.out.println("修改后原来的集合: "+org);
System.out.println("修改后复制的集合: "+copy);
} } class Employee {
private String name;
private String designation; public Employee(String name, String designation) {
this.name = name;
this.designation = designation;
} public String getDesignation() {
return designation;
} public void setDesignation(String designation) {
this.designation = designation;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return String.format("%s: %s", name, designation );
} }

执行结果:

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

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

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

 public class CollectionCloningTest {

     public static void main(String[] args){
ArrayList<Employee> org = new ArrayList<Employee>();
org.add(new Employee("Joe", "Manager"));
org.add(new Employee("Tim", "Developer"));
org.add(new Employee("Frank", "Developer")); //Collection<Employee> copy = new HashSet<>(org);
Collection<Employee> copy = new HashSet<Employee>(org.size()); System.out.println("原来的集合: "+org);
System.out.println("复制的集合: "+copy); Iterator<Employee> orgItr = org.iterator();
while(orgItr.hasNext()){
//orgItr.next().setDesignation("staff");
copy.add(orgItr.next().clone()); } Iterator<Employee> orgItr2 = org.iterator();
while(orgItr2.hasNext()){
orgItr2.next().setDesignation("staff");
}
System.out.println("修改后原来的集合: "+org);
System.out.println("修改后复制的集合: "+copy);
} } class Employee implements Cloneable{
private String name;
private String designation; public Employee(String name, String designation) {
this.name = name;
this.designation = designation;
} public String getDesignation() {
return designation;
} public void setDesignation(String designation) {
this.designation = designation;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return String.format("%s: %s", name, designation );
} @Override
protected Employee clone(){
try {
Employee result = (Employee) super.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e); // won't happen
} }
}

执行结果:

可以看到克隆集合和原始集合相互独立,它们指向不同的对象。

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

Java中如何克隆集合——ArrayList和HashSet深拷贝的更多相关文章

  1. 【集合】Java中的具体集合(一)

    Java中不止提供了集合框架中的接口,还提供了许多具体的实现. Java中的具体集合 集合类型 描述 ArrayList 一种可以动态增长和缩减的索引序列 LinkedList 一种可以在任何位置进行 ...

  2. Java中的List集合和迭代器

    一.Java中的List集合. 终于有时间来好好整理一下Java中的集合. 首先要讲的就是List集合.Java中List集合主要将两个: 第一个是底层使用数组维护的ArrayList,第二个是底层是 ...

  3. java中 列表,集合,数组之间的转换

    java中 列表,集合,数组之间的转换 java中 列表,集合,数组之间的转换 java中 列表,集合,数组之间的转换 List和Set都是接口,它们继承Collection(集合),集合里面任何数据 ...

  4. java中数组、集合、字符串之间的转换,以及用加强for循环遍历

    java中数组.集合.字符串之间的转换,以及用加强for循环遍历: @Test public void testDemo5() { ArrayList<String> list = new ...

  5. Java实例 Part6:Java中的克隆

    目录 Part6:Java中的克隆 Example01:Java对象的假克隆 Example02:Java对象的浅克隆 Example03:Java对象的深克隆 Example04:序列化与对象克隆 ...

  6. JAVA中所有与集合有关的实现类都是这六个接口的实现类

    JAVA中所有与集合有关的实现类都是这六个接口的实现类. Collection接口:集合中每一个元素为一个对象,这个接口将这些对象组织在一起,形成一维结构. List接口代表按照元素一定的相关顺序来组 ...

  7. java - day011 - 集合, ArrayList HashMap,HashSet, Iterator 接口, for-each 循环格式

    集合 ArrayList 丑数: 能被3,5,7整除多次, ArrayList     list 接口             | - ArrayList             | - Linked ...

  8. java基础33 Set集合下的HashSet集合和TreeSet集合

    单例集合体系: ---------| collection  单例集合的根接口--------------| List  如果实现了list接口的集合类,具备的特点:有序,可重复       注:集合 ...

  9. java中数组以及集合

    java中数组: 数组在Java里是一种特殊类型,有别于普通的“类的实例”的对象.但实际数组也是一种对象类型,int[]a = new int[5]  a是在java栈中分配的引用变量,类型是int[ ...

随机推荐

  1. windows+caffe(六)——convert.bat

    convert.bat的格式为 convert_imageset.exe的位置+空格+FLAGS+空格+图片所在的位置+空格+你生成的list的位置+空格+将要生成的db格式要保存的位置 建议都使用绝 ...

  2. AngularJS 学习

    原文链接: https://www.zybuluo.com/frank-shaw/note/509653 Promises in AngularJS, Explained as a Cartoon h ...

  3. 4W1T教程1 如何使用幻灯片

    第一步,读取类别为xxXX前五张幻灯片 <!-- 幻灯片循环开始-->{section name=banner loop=$banner} <li data-transition=& ...

  4. css优先级问题

    关于CSS specificityCSS 的specificity 特性或称非凡性,它是衡量一个衡量CSS值优先级的一个标准,既然作为标准,就具有一套相关的判定规定及计算方式,specificity用 ...

  5. Java开发中经典的小实例-(二分法)

    public int binarySearch(int[] data,int aim){//以int数组为例,aim为需要查找的数 int start = 0; int end = data.leng ...

  6. ObjectStateManager 中已存在具有同一键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象。

    问题:ObjectStateManager 中已存在具有同一键的对象.ObjectStateManager 无法跟踪具有相同键的多个对象. 解决方案:在查询的时候加上AsNoTracking()就ok ...

  7. RealSense开发-Session和SenseManager的几种创建方法

    从Intel RealSense 的SDK文档对其架构(如图1所示)的始描述可知,Session是SDK应用的主控模块,必须在所有模块操作之前创建,并且在所有模块注销后最后注销.SenseManage ...

  8. linux应用程序开发-进程通信(IPC)

    IPC why: 1.数据传输 2.资源共享 目的: 3.通知事件 4.进程控制 发展: 1.UNIX进程间通信 2.基于SYStem V 3.POSIX 方式分类: 1.pipe(管道) FIFO( ...

  9. ajax提交表单+前端验证小示例

    <script src="http://css.jj.cn/js/jquery.js" type="text/javascript"></sc ...

  10. 关于HTML5你必须知道的28个新特性,新技巧以及新技术

    1. 新的Doctype 尽管使用<!DOCTYPE html>,即使浏览器不懂这句话也会按照标准模式去渲染 2. Figure元素 用<figure>和<figcapt ...