克隆是創建對象副本的過程。 Java Object類帶有本機 clone()
方法,該方法返回現有實例的副本。由於Object是Java中的基類,所有對象默認支持克隆。
Java對象克隆
如果您想使用Java對象clone()方法,您必須實現
java.lang.Cloneable
標記界面。否則,它將在運行時拋出 CloneNotSupportedException
。還有Object clone是一個受保護的方法,所以您將不得不覆蓋它。讓我們通過一個示例程序來看看Java中的對象克隆。
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Employee implements Cloneable {
private int id;
private String name;
private Map<String, String> props;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getProps() {
return props;
}
public void setProps(Map<String, String> p) {
this.props = p;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
我們正在使用Object clone()方法,因此我們已經實現了 Cloneable
界面。我們正在調用超類的clone()方法,即Object clone()方法。
使用Object clone()方法
讓我們創建一個測試程式,使用對象的 `clone()` 方法來創建該實例的複製。
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Map;
public class CloningTest {
public static void main(String[] args) throws CloneNotSupportedException {
Employee emp = new Employee();
emp.setId(1);
emp.setName("Pankaj");
Map props = new HashMap<>();
props.put("salary", "10000");
props.put("city", "Bangalore");
emp.setProps(props);
Employee clonedEmp = (Employee) emp.clone();
// 檢查 emp 和 clonedEmp 屬性是否相同或不同
System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
// 讓我們看看使用默認克隆的效果
// 更改 emp 屬性
emp.getProps().put("title", "CEO");
emp.getProps().put("city", "New York");
System.out.println("clonedEmp props:" + clonedEmp.getProps());
// 更改 emp 名稱
emp.setName("new");
System.out.println("clonedEmp name:" + clonedEmp.getName());
}
}
輸出:
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj
在運行時的 CloneNotSupportedException
如果我們的 Employee 類不實現 Cloneable
接口,上述程序將在運行時拋出 CloneNotSupportedException
錯誤例外。
Exception in thread "main" java.lang.CloneNotSupportedException: com.journaldev.cloning.Employee
at java.lang.Object.clone(Native Method)
at com.journaldev.cloning.Employee.clone(Employee.java:41)
at com.journaldev.cloning.CloningTest.main(CloningTest.java:19)
理解對象克隆
讓我們查看上面的輸出,了解對象 clone()
方法的運作。
emp 和 clonedEmp == 測試:false
:這意味著 emp 和 clonedEmp 是兩個不同的對象,不是指向同一個對象。這符合 Java 對象克隆的要求。emp 和 clonedEmp HashMap == 測試: 真
:所以 emp 和 clonedEmp 物件變數都參考同一個物件。如果我們改變基礎物件的值,這可能是一個嚴重的資料完整性問題。任何值的變更都可能會反映到複製的實例中。clonedEmp 屬性:{city=紐約, salary=10000, title=CEO}
:我們沒有對 clonedEmp 屬性進行任何更改,但它們仍然被更改,因為 emp 和 clonedEmp 變數都參考同一個物件。這是因為默認的 Object clone() 方法創建了一個淺層拷貝。當您希望通過克隆過程創建完全分離的物件時,這可能是一個問題。這可能導致不需要的結果,因此需要適當地覆寫 Object clone() 方法。clonedEmp 名稱:Pankaj
:這裡發生了什麼?我們改變了 emp 的名稱,但 clonedEmp 的名稱沒有變。這是因為 String 是不可變的。所以當我們設置 emp 的名稱時,會創建一個新的字符串,並且 emp 的名稱引用在this.name = name;
中更改。因此 clonedEmp 的名稱保持不變。對於任何原始變數類型,您也會發現類似的行為。只要物件中只有原始和不可變變數,我們就可以使用 java 物件的默認克隆。
物件克隆類型
有兩種類型的對象克隆 – 淺克隆和深克隆。讓我們了解它們的每個並找出在我們的Java程序中實現克隆的最佳方法。
1. 淺克隆
Java Object clone() 方法的默認實現使用淺拷貝。它使用反射API來創建實例的拷貝。下面的代碼片段展示了淺拷貝的實現。
@Override
public Object clone() throws CloneNotSupportedException {
Employee e = new Employee();
e.setId(this.id);
e.setName(this.name);
e.setProps(this.props);
return e;
}
2. 深克隆
在深克隆中,我們必須逐個複製字段。如果我們有一個包含嵌套對象(例如列表、映射等)的字段,那麼我們也必須逐個編寫代碼來複製它們。這就是為什麼它被稱為深克隆或深拷貝。我們可以像下面的代碼一樣覆蓋 Employee clone 方法來實現深克隆。
public Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //utilize clone Object method
Employee emp = (Employee) obj;
// 不可變字段的深克隆
emp.setProps(null);
Map hm = new HashMap<>();
String key;
Iterator it = this.props.keySet().iterator();
// 逐字段深拷貝
while (it.hasNext()) {
key = it.next();
hm.put(key, this.props.get(key));
}
emp.setProps(hm);
return emp;
}
使用這個 clone() 方法實現,我們的測試程序將產生以下輸出。
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj
在大多數情況下,這是我們想要的。clone()方法應該返回一個完全與原始實例分離的新對象。所以如果你打算在你的程序中使用Object clone和cloning,請明智地使用並適當地覆蓋它,並注意可變字段。如果你的類擴展了其他類,而這些類又擴展了其他類,這可能是一個令人生畏的任務。你將不得不遍歷整個Object繼承層次結構,為所有可變字段進行深拷貝。
使用序列化進行克隆?
一種簡單實現深拷貝的方式是通過序列化。但是序列化是一個耗時的過程,你的類應該實現Serializable接口。所有的字段和父類也必須實現Serializable接口。
使用Apache Commons Util
如果你已經在項目中使用Apache Commons Util類,並且你的類是可序列化的,那麼可以使用以下方法。
Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);
複製用於克隆的複製構造函數
我們可以定義一個複製構造函數來創建對象的副本。為什麼要完全依賴於Object的clone()方法呢?例如,我們可以像以下代碼一樣擁有一個Employee複製構造函數。
public Employee(Employee emp) {
this.setId(emp.getId());
this.setName(emp.getName());
Map hm = new HashMap<>();
String key;
Iterator it = emp.getProps().keySet().iterator();
// 逐個字段進行深拷貝
while (it.hasNext()) {
key = it.next();
hm.put(key, emp.getProps().get(key));
}
this.setProps(hm);
}
每當我們需要員工對象的副本時,我們可以使用Employee clonedEmp = new Employee(emp);
來獲取。但是如果你的類有很多變量,特別是基本類型和不可變類型,寫複製構造函數可能會是一件繁瑣的工作。
Java對象克隆的最佳實踐
-
只有當你的類具有基本類型和不可變變量,或者你想要淺拷貝時,才使用默認的Object clone()方法。在繼承的情況下,你將不得不檢查所有你正在擴展的類,直到Object級別。
-
如果您的類別主要具有可變屬性,您也可以定義複製構造函數。
-
通過在覆蓋的複製方法中調用
super.clone()
,然後對可變字段進行必要的更改,利用Object clone()方法來實現深層複製。 -
如果您的類別是可序列化的,您可以使用序列化進行克隆。但是,這將導致性能下降,因此在使用序列化進行克隆之前,請先進行一些基準測試。
-
如果您正在擴展一個類,並且它已經正確地使用深拷貝定義了clone方法,那麼您可以利用默認的clone方法。例如,我們在Employee類中正確定義了clone()方法如下。
@Override public Object clone() throws CloneNotSupportedException { Object obj = super.clone(); Employee emp = (Employee) obj; // 對於不可變字段的深層克隆 emp.setProps(null); Map<String, String> hm = new HashMap<>(); String key; Iterator<String> it = this.props.keySet().iterator(); // 逐個字段進行深拷貝 while (it.hasNext()) { key = it.next(); hm.put(key, this.props.get(key)); } emp.setProps(hm); return emp; }
我們可以創建一個子類並利用超類的深克隆,如下所示。
package com.journaldev.cloning; public class EmployeeWrap extends Employee implements Cloneable { private String title; public String getTitle() { return title; } public void setTitle(String t) { this.title = t; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
EmployeeWrap
類沒有任何可變屬性,並且它正在使用超類的clone()方法實現。如果存在可變字段,則必須謹慎處理僅這些字段的深拷貝。這裡是一個簡單的程序,用來測試這種克隆方式是否正常工作。package com.journaldev.cloning; import java.util.HashMap; import java.util.Map; public class CloningTest { public static void main(String[] args) throws CloneNotSupportedException { EmployeeWrap empWrap = new EmployeeWrap(); empWrap.setId(1); empWrap.setName("Pankaj"); empWrap.setTitle("CEO"); Map<String, String> props = new HashMap<>(); props.put("salary", "10000"); props.put("city", "Bangalore"); empWrap.setProps(props); EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone(); empWrap.getProps().put("1", "1"); System.out.println("empWrap mutable property value = "+empWrap.getProps()); System.out.println("clonedEmpWrap mutable property value = "+clonedEmpWrap.getProps()); } }
輸出:
empWrap mutable property value = {1=1, city=Bangalore, salary=10000} clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}
所以它完全按照我們的預期工作。
這就是Java中對象克隆的全部內容。我希望您對Java對象clone()方法以及如何正確覆蓋它而不產生任何負面影響有了一些了解。
您可以從我的 GitHub存儲庫 下載該項目。
Source:
https://www.digitalocean.com/community/tutorials/java-clone-object-cloning-java