原文网址:http://blog.csdn.net/cool_sti/article/details/21658521

原英文链接:http://javarevisited.blogspot.hk/2014/03/how-to-clone-collection-in-java-deep-copy-vs-shallow.html

程序员通常会误用集合类(如List、Set、ArrayList、HashSet)所提供的拷贝构造函数或其它方法来完成集合的拷贝。值得记住的一点是,Java中集合提供的拷贝构造函数只支持浅拷贝而不是深拷贝,这意味着存储在原有List和克隆List中的对象会保持一致,并指向Java堆中同一内存地址。造成这一误解的原因是它采用Collections对不可变对象进行了浅拷贝,正是由于对象不可变,两个集合指向相同对象也是合乎情理的。正是如此,所以保存在池中的String,更新其中一个不会影响到其它对象。问题来了,当我们使用ArrayList的拷贝构造函数来创建Employee对象列表的克隆时,其中Employee对象是可变的,在这个例子中如果原有集合修改了其中一个Employee,改变同样也会发生在拷贝的集合中,但这并不是我们所希望的。在几乎所有的例子中,克隆应该和原有对象独立开来。能够避免这个问题的方法是采用深度拷贝集合,即递归拷贝对象直到访问到原语或不可变对象。在这篇文章中,我们将介绍一种深度拷贝集合的方法,如Java中的ArrayList或HashSet。顺便提一下,如果你了解浅拷贝和深拷贝之间的区别,那么你会很轻松地掌握深度拷贝集合的机理。

Java集合中的深度拷贝

在下面的例子中,我们有一个可变Employee对象的集合,每个对象包含name和designation字段,将它们保存在HashSet中。我们用java.util.Collection接口中的addAll()方法来创建这个集合的一个拷贝。在这之后,我们修改原有集合中的每个Employee对象的designation字段,希望这个改变不会影响到拷贝集合,但事与愿违,解决这个问题的方法是深度拷贝集合类中的元素。
  1. import java.util.Collection;
  2. import java.util.HashSet;
  3. import java.util.Iterator;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. /**
  7. * Java program to demonstrate copy constructor of Collection provides shallow
  8. * copy and techniques to deep clone Collection by iterating over them.
  9. * @author http://javarevisited.blogspot.com
  10. */
  11. public class CollectionCloningTest {
  12. private static final Logger logger = LoggerFactory.getLogger(CollectionCloningclass);
  13. public static void main(String args[]) {
  14. // deep cloning Collection in Java
  15. Collection org = new HashSet();
  16. org.add(new Employee("Joe", "Manager"));
  17. org.add(new Employee("Tim", "Developer"));
  18. org.add(new Employee("Frank", "Developer"));
  19. // creating copy of Collection using copy constructor
  20. Collection copy = new HashSet(org);
  21. logger.debug("Original Collection {}", org);
  22. logger.debug("Copy of Collection {}", copy );
  23. Iterator itr = org.iterator();
  24. while(itr.hasNext()){
  25. itr.next().setDesignation("staff");
  26. }
  27. logger.debug("Original Collection after modification {}", org);
  28. logger.debug("Copy of Collection without modification {}", copy );
  29. // deep Cloning List in Java
  30. }
  31. }
  32. class Employee {
  33. private String name;
  34. private String designation;
  35. public Employee(String name, String designation) {
  36. this.name = name; this.designation = designation;
  37. }
  38. public String getDesignation() {
  39. return designation;
  40. }
  41. public void setDesignation(String designation) {
  42. this.designation = designation;
  43. }
  44. public String getName() {
  45. return name;
  46. }
  47. public void setName(String name) {
  48. this.name = name;
  49. }
  50. @Override public String toString() {
  51. return String.format("%s: %s", name, designation );
  52. }
  53. }

输出:

  1. - Original Collection [Joe: Manager, Frank: Developer, Tim: Developer]
  2. - Copy of Collection [Joe: Manager, Frank: Developer, Tim: Developer]
  3. - Original Collection after modification [Joe: staff, Frank: staff, Tim: staff]
  4. - Copy of Collection without modification [Joe: staff, Frank: staff, Tim: staff]
可以很清楚地看到修改原有集合的Employee对象(即修改designation字段为"staff")也会反映到拷贝集合中,因为克隆是浅拷贝,它指向的是堆中同一个Employee对象。为了修复这个问题,我们需要遍历集合深度拷贝所有Employee对象,而在这之前,我们需要为Employee对象覆写clone方法。
1)让Employee类实现Cloneable接口;
2)在Employee类中添加下面的clone()方法;
  1. @Override
  2. protected Employee clone() {
  3. Employee clone = null;
  4. try{
  5. clone = (Employee) super.clone();
  6. }catch(CloneNotSupportedException e){
  7. throw new RuntimeException(e);  // won't happen
  8. }
  9. return clone;
  10. }
3)使用下面的代码,用Java中深度拷贝取代拷贝构造函数;
  1. Collection<Employee> copy = new HashSet<Employee>(org.size());
  2. Iterator<Employee> iterator = org.iterator();
  3. while(iterator.hasNext()){
  4. copy.add(iterator.next().clone());
  5. }
4)为修改后的集合运行同样的代码,会得到不同的输出:
  1. - Original Collection after modification  [Joe: staff, Tim: staff, Frank: staff]
  2. - Copy of Collection without modification [Frank: Developer, Joe: Manager, Tim: Developer]
可以看出克隆后的和原有集合相互独立,分别指向不同的对象。
这便是Java如何克隆集合的所有内容,现在我们明白集合类中各种拷贝构造函数,如List或Set中addAll()方法,仅仅实现了集合的浅拷贝,即原有集合和拷贝的集合指向相同的对象。这也便要求在集合中存储任何对象,必须支持深度拷贝操作。
 
PS: 原Blog中的代码执行可能会遇到一些问题,但关键是这中间的思想很重要。
 
链接:

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

  1. Java对象和集合的拷贝/克隆/复制

    昨天同事遇到了一个奇怪的问题,他需要将一个JavaBean拷贝一份,然后对新创建的Bean进行操作.但是他对新的Bean操作后,会影响旧的Bean的值.当听到这个问题的时候,我第一反应就是他的拷贝方法 ...

  2. Java中的集合List、ArrayList、Vector、Stack(三)

    List接口 List集合代表一个有序集合,集合中每一个元素都有其对应的顺序索引.List集合容许使用重复元素,可以通过索引来访问指定位置的集合对象. ArrayList和Vector实现类 Arra ...

  3. java集合专题 (ArrayList、HashSet等集合底层结构及扩容机制、HashMap源码)

    一.数组与集合比较 数组: 1)长度开始时必须指定,而且一旦指定,不能更改 2)保存的必须为同一类型的元素 3)使用数组进行增加/删除元素-比较麻烦 集合: 1)可以动态保存任意多个对象,使用比较方便 ...

  4. java基础之集合框架--使用ArrayList类动态 存储数据

    一.ArrayList是List接口下的一个实现类,实现了长度可变的.连续的数组:拥有数组的特性. 遵循了LIst的规则:不唯一的.有序的. 如果没有增加泛型的话,集合中可以添加任何类型的数据. 使用 ...

  5. Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap

    声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...

  6. java基础之集合长度可变的实现原理

    首先我们要明白java中的集合Collection,List,ArrayList之间的关系: ArrayList是具体的实现类,实现了List接口 List是接口,继承了Collection接口 Li ...

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

    编程人员经常误用各个集合类提供的拷贝构造函数作为克隆List,Set,ArrayList,HashSet或者其他集合实现的方法.需要记住的是,Java集合的拷贝构造函数只提供浅拷贝而不是深拷贝,这意味 ...

  8. java集合系列之三(ArrayList)

    上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...

  9. Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

随机推荐

  1. Java程序员在用的大数据工具,MongoDB稳居第一!

    据日前的一则大数据工具使用情况调查,我们知道了Java程序猿最喜欢用的大数据工具. 问题:他们最近一年最喜欢用什么工具或者是框架? 受访者可以选择列表中的选项或者列出自己的,本文主要关心的是大数据工具 ...

  2. Java实现生命周期管理机制

    先扯再说 最近一直在研究某个国产开源的MySQL数据库中间件,拉下其最新版的代码到eclipse后,启动起来,然后做各种测试和代码追踪:用完想要关闭它时,拉出它的STOP类想要运行时,发现这个类里赫然 ...

  3. switchover和failover

    Dataguard中primary和standby间的角色切换包括两种:1. switchoverprimary和standby互换角色,一般都是人为的有计划的,主要用于主机或数据库的升级,不会有数据 ...

  4. [Android] PorterDuff使用实例----实现新浪微博图片下载效果

    先上效果图,如demo_sinaweibo.gif 由效果图,下半部分是简单的效果叠加,上半部分是新浪微博加载图片显示进度的效果,显示进度的半透明区域只与根据背景图的非透明区域叠加,背景图的透明区域仍 ...

  5. [Polymer] Custom Elements: Styling

    Code: <dom-module id="business-card"> <template> <div class="card" ...

  6. ubuntu14.04 install flow.

    打开虚拟机,点击菜单上的“文件”,选择新建虚拟机,如下图所示: 注释:这里选择自定义安装,点击下一步. 这里我的虚拟机版本最新是10的,就选最新的,然后点击下一步,如下图: 这里选择要安装的Ubunt ...

  7. VLV INDEX

    Normally, when the Directory Server conducts a search, the server looks through the entire entry for ...

  8. windows快捷键命令汇总整理

        访问当前屏幕的设置,打开"设置"栏 按 Windows 徽标键+"i"打开当前屏幕(例如,"开始".桌面或 Windows 应用)的 ...

  9. pd的django个人博客教程----1:效果展示等

    开发环境同to do list 1:首页:localhost/pd/ 2:导航栏测试或者生活点入: 测试:localhost/category/?cid=1 3:点击文章后进入文章显示页面 e.g:进 ...

  10. 解决iOS中tabBarItem图片默认颜色的问题(指定代码渲染模式为以原样模式的方式显示出来)

    解决iOS中tabBarItem图片默认颜色的问题(指定代码渲染模式为以原样模式的方式显示出来) 解决办法:指定图片的渲染模式(imageWithRenderingMode为:UIImageRende ...