原文網址: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字段,希望這個改變不會影響到拷貝集合,但事與願違,解決這個問題的方法是深度拷貝集合類中的元素。
- import java.util.Collection;
- import java.util.HashSet;
- import java.util.Iterator;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * Java program to demonstrate copy constructor of Collection provides shallow
- * copy and techniques to deep clone Collection by iterating over them.
- * @author http://javarevisited.blogspot.com
- */
- public class CollectionCloningTest {
- private static final Logger logger = LoggerFactory.getLogger(CollectionCloningclass);
- public static void main(String args[]) {
- // deep cloning Collection in Java
- Collection org = new HashSet();
- org.add(new Employee("Joe", "Manager"));
- org.add(new Employee("Tim", "Developer"));
- org.add(new Employee("Frank", "Developer"));
- // creating copy of Collection using copy constructor
- Collection copy = new HashSet(org);
- logger.debug("Original Collection {}", org);
- logger.debug("Copy of Collection {}", copy );
- Iterator itr = org.iterator();
- while(itr.hasNext()){
- itr.next().setDesignation("staff");
- }
- logger.debug("Original Collection after modification {}", org);
- logger.debug("Copy of Collection without modification {}", copy );
- // deep Cloning List in Java
- }
- }
- 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 );
- }
- }
- - Original Collection [Joe: Manager, Frank: Developer, Tim: Developer]
- - Copy of Collection [Joe: Manager, Frank: Developer, Tim: Developer]
- - Original Collection after modification [Joe: staff, Frank: staff, Tim: staff]
- - Copy of Collection without modification [Joe: staff, Frank: staff, Tim: staff]
可以很清楚地看到修改原有集合的Employee對象(即修改designation字段為"staff")也會反映到拷貝集合中,因為克隆是淺拷貝,它指向的是堆中同一個Employee對象。為了修復這個問題,我們需要遍歷集合深度拷貝所有Employee對象,而在這之前,我們需要為Employee對象覆寫clone方法。
1)讓Employee類實現Cloneable接口;
2)在Employee類中添加下面的clone()方法;
- @Override
- protected Employee clone() {
- Employee clone = null;
- try{
- clone = (Employee) super.clone();
- }catch(CloneNotSupportedException e){
- throw new RuntimeException(e); // won't happen
- }
- return clone;
- }
3)使用下面的代碼,用Java中深度拷貝取代拷貝構造函數;
- Collection<Employee> copy = new HashSet<Employee>(org.size());
- Iterator<Employee> iterator = org.iterator();
- while(iterator.hasNext()){
- copy.add(iterator.next().clone());
- }
4)為修改后的集合運行同樣的代碼,會得到不同的輸出:
- - Original Collection after modification [Joe: staff, Tim: staff, Frank: staff]
- - Copy of Collection without modification [Frank: Developer, Joe: Manager, Tim: Developer]
可以看出克隆后的和原有集合相互獨立,分別指向不同的對象。

這便是Java如何克隆集合的所有內容,現在我們明白集合類中各種拷貝構造函數,如List或Set中addAll()方法,僅僅實現了集合的淺拷貝,即原有集合和拷貝的集合指向相同的對象。這也便要求在集合中存儲任何對象,必須支持深度拷貝操作。
PS: 原Blog中的代碼執行可能會遇到一些問題,但關鍵是這中間的思想很重要。
鏈接: