編程人員經常誤用各個集合類提供的拷貝構造函數作為克隆List,Set,ArrayList,HashSet或者其他集合實現的方法。需要記住的是,Java集合的拷貝構造函數只提供淺拷貝而不是深拷貝,這意味著存儲在原始List和克隆List中的對象是相同的,指向Java堆內存中相同的位置。增加了這個誤解的原因之一是對于不可變對象集合的淺克隆。由于不可變性,即使兩個集合指向相同的對象是可以的。字符串池包含的字符串就是這種情況,更改一個不會影響到另一個。使用ArrayList的拷貝構造函數創建雇員List的拷貝時就會出現問題,Employee類不是不可變的。在這種情況下,如果原始集合修改了雇員信息,這個變化也將反映到克隆集合。同樣如果克隆集合雇員信息發生變化,原始集合也會被更改。絕大多數情況下,這種變化不是我們所希望的,克隆對象應該與原始對象獨立。解決這個問題的方法是深克隆集合,深克隆將遞歸克隆對象直到基本數據類型或者不可變類。本文將了解一下深拷貝ArrayList或者HashSet等集合類的一種方法。如果你了解深拷貝與淺拷貝之間的區別,那么理解集合深克隆的方法就會很簡單。
Java集合的深克隆
下面例子有一個Employee集合,Employee是可變對象,成員變量name和designation。它們存儲在HashSet中。使用java.util.Collection接口的addAll()方法創建集合拷貝。然后修改存儲在原始集合每個Employee對象的designation值。理想情況下這個改變不會影響克隆集合,因為克隆集合和原始集合應該相互獨立,但是克隆集合也被改變了。修正這個問題的方法是對存儲在Collection類中的元素深克隆。
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<Employee> 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<Employee> copy = new HashSet<>(org);
logger.debug("Original Collection {}", org);
logger.debug("Copy of Collection {}", copy );
Iterator<Employee> 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]
可以看到改變原始Collection中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)不使用拷貝構造函數,使用下面的代碼來深拷貝集合
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()方法僅僅創建了集合的淺拷貝,而且原始集合和克隆集合指向相同的對象。為避免這個問題,應該深克隆集合,遍歷集合克隆每個元素。盡管這要求集合中的對象必須支持深克隆操作。
原文鏈接: javarevisited 翻譯: ImportNew.com - hejiani
譯文鏈接: http://www.importnew.com/10761.html
[ 轉載請保留原文出處、譯者和譯文鏈接。]
- JVM
- 深入理解Java內存模型
- 深入理解Java內存模型(一)——基礎
- 深入理解Java內存模型(二)——重排序
- 深入理解Java內存模型(三)——順序一致性
- 深入理解Java內存模型(四)——volatile
- 深入理解Java內存模型(五)——鎖
- 深入理解Java內存模型(六)——final
- 深入理解Java內存模型(七)——總結
- Java內存模型
- Java內存模型2
- 堆內內存還是堆外內存?
- JVM內存配置詳解
- Java內存分配全面淺析
- 深入Java核心 Java內存分配原理精講
- jvm常量池
- JVM調優總結
- JVM調優總結(一)-- 一些概念
- JVM調優總結(二)-一些概念
- VM調優總結(三)-基本垃圾回收算法
- JVM調優總結(四)-垃圾回收面臨的問題
- JVM調優總結(五)-分代垃圾回收詳述1
- JVM調優總結(六)-分代垃圾回收詳述2
- JVM調優總結(七)-典型配置舉例1
- JVM調優總結(八)-典型配置舉例2
- JVM調優總結(九)-新一代的垃圾回收算法
- JVM調優總結(十)-調優方法
- 基礎
- Java 征途:行者的地圖
- Java程序員應該知道的10個面向對象理論
- Java泛型總結
- 序列化與反序列化
- 通過反編譯深入理解Java String及intern
- android 加固防止反編譯-重新打包
- volatile
- 正確使用 Volatile 變量
- 異常
- 深入理解java異常處理機制
- Java異常處理的10個最佳實踐
- Java異常處理手冊和最佳實踐
- Java提高篇——對象克隆(復制)
- Java中如何克隆集合——ArrayList和HashSet深拷貝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常見正則表達式
- 類
- 理解java類加載器以及ClassLoader類
- 深入探討 Java 類加載器
- 類加載器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之實現細節
- java.util.concurrent 之ConcurrentHashMap 源碼分析
- HashMap的實現原理和底層數據結構
- 線程
- 關于Java并發編程的總結和思考
- 40個Java多線程問題總結
- Java中的多線程你只要看這一篇就夠了
- Java多線程干貨系列(1):Java多線程基礎
- Java非阻塞算法簡介
- Java并發的四種風味:Thread、Executor、ForkJoin和Actor
- Java中不同的并發實現的性能比較
- JAVA CAS原理深度分析
- 多個線程之間共享數據的方式
- Java并發編程
- Java并發編程(1):可重入內置鎖
- Java并發編程(2):線程中斷(含代碼)
- Java并發編程(3):線程掛起、恢復與終止的正確方法(含代碼)
- Java并發編程(4):守護線程與線程阻塞的四種情況
- Java并發編程(5):volatile變量修飾符—意料之外的問題(含代碼)
- Java并發編程(6):Runnable和Thread實現多線程的區別(含代碼)
- Java并發編程(7):使用synchronized獲取互斥鎖的幾點說明
- Java并發編程(8):多線程環境中安全使用集合API(含代碼)
- Java并發編程(9):死鎖(含代碼)
- Java并發編程(10):使用wait/notify/notifyAll實現線程間通信的幾點重要說明
- java并發編程-II
- Java多線程基礎:進程和線程之由來
- Java并發編程:如何創建線程?
- Java并發編程:Thread類的使用
- Java并發編程:synchronized
- Java并發編程:Lock
- Java并發編程:volatile關鍵字解析
- Java并發編程:深入剖析ThreadLocal
- Java并發編程:CountDownLatch、CyclicBarrier和Semaphore
- Java并發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
- Synchronized與Lock
- JVM底層又是如何實現synchronized的
- Java synchronized詳解
- synchronized 與 Lock 的那點事
- 深入研究 Java Synchronize 和 Lock 的區別與用法
- JAVA編程中的鎖機制詳解
- Java中的鎖
- TreadLocal
- 深入JDK源碼之ThreadLocal類
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的內存泄露
- 多線程設計模式
- Java多線程編程中Future模式的詳解
- 原子操作(CAS)
- [譯]Java中Wait、Sleep和Yield方法的區別
- 線程池
- 如何合理地估算線程池大小?
- JAVA線程池中隊列與池大小的關系
- Java四種線程池的使用
- 深入理解Java之線程池
- java并發編程III
- Java 8并發工具包漫游指南
- 聊聊并發
- 聊聊并發(一)——深入分析Volatile的實現原理
- 聊聊并發(二)——Java SE1.6中的Synchronized
- 文件
- 網絡
- index
- 內存文章索引
- 基礎文章索引
- 線程文章索引
- 網絡文章索引
- IOC
- 設計模式文章索引
- 面試
- Java常量池詳解之一道比較蛋疼的面試題
- 近5年133個Java面試問題列表
- Java工程師成神之路
- Java字符串問題Top10
- 設計模式
- Java:單例模式的七種寫法
- Java 利用枚舉實現單例模式
- 常用jar
- HttpClient和HtmlUnit的比較總結
- IO
- NIO
- NIO入門
- 注解
- Java Annotation認知(包括框架圖、詳細介紹、示例說明)