首先我們看看淺拷貝和深拷貝的定義
淺拷貝:隻複制一個對象,對象内部存在的指向其他對象數組或者引用則不複制
深拷貝:對象,對象内部的引用均複制
為了更好的了解它們的差別我們假設有一個對象A,它包含有2對象,對象A1和對象A2
對象A進行淺拷貝後,得到對象B但是對象A1和A2并沒有被拷貝
對象A進行深拷貝,得到對象B的同時A1和A2連同它們的引用也被拷貝
在了解了深拷貝和淺拷貝後,我們來看看Java的深拷貝和淺拷貝實作.
Object 類的 clone方法執行特定的克隆操作。
首先,如果此對象的類不能實作接口 Cloneable,則會抛出 CloneNotSupportedException。(注意:所有的數組都被視為實作接口 Cloneable)
此方法會建立此對象的類的一個新執行個體,并像通過配置設定,嚴格使用此對象相應字段的内容初始化該對象的所有字段;這些字段的内容沒有被自我克隆。是以,此方法執行的是該對象的“淺表複制”,而不“深層複制”操作。
Object 類本身不實作接口 Cloneable,是以在類為 Object的對象上調用 clone 方法将會導緻在運作時抛出異常。
下面從三個複制的執行個體代碼來看它們之間的差別,我們需建立Person類:
Java代碼
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iY4QGM3EjY5cjM5M2NkJTOjdDZhNmMhNGZ3IDN0cjNm9CX0EzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL2M3Lc9CX6MHc0RHaiojIsJye.png)
public class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setAge(int age) {
public void setName(String name) {
public void display() {
System.out.println("Name:" + name + "/tAge:" + age);
}
一、普通複制
即我們最容易想到的将一個對象指派給另外一個對象。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iY4QGM3EjY5cjM5M2NkJTOjdDZhNmMhNGZ3IDN0cjNm9CX0EzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL2M3Lc9CX6MHc0RHaiojIsJye.png)
public static void main(String[] args) {
Person p1=new Person("jack",20);
Person p2=p1;
p1.setAge(49);//簡單複制
p2.display();
p1.display();
System.out.println(p1);
System.out.println(p2);
結果: Name:jack Age:49
Name:jack Age:49
net.pcedu.clone.Person@c17164
說明p1和p2對象的是同一個引用,是以再改屬性2個都是改變的。
二、淺拷貝
淺拷貝必需對Book類實作Cloneable接口的clone方法
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iY4QGM3EjY5cjM5M2NkJTOjdDZhNmMhNGZ3IDN0cjNm9CX0EzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL2M3Lc9CX6MHc0RHaiojIsJye.png)
public class Book implements Cloneable{
String bookName;
double price;
Person author;
public Book(String bn,double price,Person author){
bookName = bn;
this.price = price;
this.author = author;
}
public Object clone(){
Book b = null;
try{
b = (Book)super.clone();
}catch(CloneNotSupportedExceptione){
e.printStackTrace();
}
return b;
}
public void display(){
System.out.print(bookName + "/t" +price + "/t") ;
author.display();
}
publicstatic void main(Stringargs[]){
Book b1 = new Book("Java程式設計",30.50,new Person("張三",34));
Book b2 = (Book)b1.clone();
b2.price = 44.0;
b2.author.setAge(45);
b2.author.setName("李四");
b2.bookName = "Java開發";
b1.display();
b2.display();
結果:
Java程式設計 30.5 Name:李四 Age:45
Java開發 44.0 Name:李四 Age:45
說明b1和b2是不同的對象,但是b1.author和b2.author指向同一對象,。
問題如下: 發現在改變b2的author對象屬性時b1的author對象的屬性也改變了,說明在淺拷貝中的author這個對象沒有被完全拷貝,而是使用同一引用,這樣就要使用深拷貝了。
三、深拷貝
為了解決如上問題,我們需要用到深拷貝,其實很簡單在拷貝book對象的時候加入如下語句
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iY4QGM3EjY5cjM5M2NkJTOjdDZhNmMhNGZ3IDN0cjNm9CX0EzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL2M3Lc9CX6MHc0RHaiojIsJye.png)
b.author =(Person)author.clone(); //将Person對象進行拷貝,Person對象需進行了拷貝
在運作上面的main方法,結果如下:
Java程式設計30.5 Name:張三 Age:34
Java開發44.0 Name:李四 Age:45
說明b1和b2是不同的對象,b1.author和b2.author指向不同對象,。(含引用對象屬性的拷貝)。
java.lang.t的clone()方法預設是傳回一個前拷貝對象。是以如果要用clone()方法實作一個深拷貝,我們必須對每個對象的clone()方法進行特别實作。當對象層次複雜的時候,這樣做不但困難而且浪費時間和容易出現錯誤,特别有時候你不但需要深拷貝同時你也對這個對象進行淺拷貝的時候,你會發現寫這個clone()方法真不是一個好的解決方案。
那麼除了clone()方法,我們還可以怎麼實作呢?答案是序列化
序列化的對象要實作Serializable接口才能實作序列化,同時,對象的成員對象也要實作序列化.
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iY4QGM3EjY5cjM5M2NkJTOjdDZhNmMhNGZ3IDN0cjNm9CX0EzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL2M3Lc9CX6MHc0RHaiojIsJye.png)
A a=new A();
//寫對象,序列化
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out= new ObjectOutputStream(byteOut);
out.writeObject(a);
//讀對象,反序列化
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
A b=(A)in.readObject();
序列化經常用于檔案傳遞的讀取。尤其是在緩存中用得比較多,通過序列化可以将對象緩存在硬碟中。這在登入系統緩存使用者權限和角色等資訊最常見。而用對克隆對象,也不失為一種很好的方法。
java.lang.Object類的clone方法是一個protected方法,在子類需要重寫此方法并聲明為public類型,而且還需實作Cloneable接口才能提供對象複制的能力,clone()是一個native方法,native方法的效率一般來說都是遠高于java中的非native方法,對性能比較關心的話首先考慮這種方式,另一種方式——通過java的反射機制複制對象,這種方式效率可能會比clone()低,而且不支援深度複制以及複制集合類型,但通用性會提高很多,下邊是進行複制的代碼:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iY4QGM3EjY5cjM5M2NkJTOjdDZhNmMhNGZ3IDN0cjNm9CX0EzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL2M3Lc9CX6MHc0RHaiojIsJye.png)
private <T> T getBean(T TargetBean, T SourceBean) {
if (TargetBean== null) return null;
Field[] tFields = TargetBean.getClass().getDeclaredFields();
Field[] sFields = SourceBean.getClass().getDeclaredFields();
try {
for (Field field : tFields ) {
String fieldName = field.getName();
if (fieldName.equals("serialVersionUID")) continue;
if (field.getType() == Map.class) continue;
if (field.getType() == Set.class) continue;
if (field.getType() == List.class) continue;
for (Field sField : sFields) {
if(!sField .getName().equals(fieldName)){
continue;
}
Class type = field.getType();
String setName = getSetMethodName(fieldName);
Method tMethod = TargetBean.getClass().getMethod(setName, new Class[]{type});
String getName = getGetMethodName(fieldName);
Method sMethod = SourceBean.getClass().getMethod(getName, null);
Object setterValue = voMethod.invoke(SourceBean, null);
tMethod.invoke(TargetBean, new Object[]{setterValue});
}
}
} catch (Exception e) {
throw new Exception("設定參數資訊發生異常", e);
return TargetBean;
該方法接收兩個參數,一個是複制的源對象——要複制的對象,一個是複制的目标對象——對象副本,當然這個方法也可以在兩個不同對象間使用,這時候隻要目标對象和對象具有一個或多個相同類型及名稱的屬性,那麼就會把源對象的屬性值賦給目标對象的屬性。