Java 的深拷貝和淺拷貝
什么是深拷貝贸伐、淺拷貝 (深克隆勾给、淺克隆)恍涂?
在 Java 中,數(shù)據(jù)類型分為 基本數(shù)據(jù)類型 和 引用數(shù)據(jù)類型足丢,深/淺拷貝是針對于 引用數(shù)據(jù)類型 來說的粱腻。
-
淺拷貝:對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型進行引用傳遞般的拷貝稱為淺拷貝斩跌。通過實現(xiàn)Cloneable接口并重寫Object類中的clone()方法可以實現(xiàn)淺克隆绍些。
淺拷貝.png -
深拷貝:對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型耀鸦,創(chuàng)建一個新的對象柬批,并復制其內(nèi)容稱為深拷貝。
深拷貝.png
如何進行深拷貝袖订?
- 實現(xiàn)Cloneable接口并重寫Object類及成員變量中引用類型的的clone()方法
- 實現(xiàn)Serializable接口氮帐,通過對象的序列化和反序列化實現(xiàn)克隆,可以實現(xiàn)真正的深度克隆
注意:基于序列化和反序列化實現(xiàn)的拷貝不僅僅是深度克隆洛姑,更重要的是通過泛型限定上沐,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的楞艾,不是在運行時拋出異常参咙,這種是方案明顯優(yōu)于使用Object類的clone方法克隆對象龄广。讓問題在編譯的時候暴露出來總是優(yōu)于把問題留到運行時。
舉個栗子
實現(xiàn)Cloneable接口 : 淺克隆 vs 深克隆
- 淺克隆
@Getter
@AllArgsConstructor
public class Sub {
private String sName;
}
@AllArgsConstructor
public class Parent implements Cloneable {
private String pName;
private Sub sub;
@Override
protected Object clone() {
Parent parent = null;
try {
parent = (Parent) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return parent;
}
public static void main(String[] args) {
Sub sub = new Sub("sName");
Parent parent = new Parent("pName", sub);
Parent clone = (Parent) parent.clone();
System.out.println("==============parent===================");
System.out.println(parent.hashCode());
System.out.println(parent.pName);
System.out.println(parent.sub.hashCode());
System.out.println(parent.sub.getSName());
System.out.println("================clone===================");
System.out.println(clone.hashCode());
System.out.println(clone.pName);
System.out.println(clone.sub.hashCode());
System.out.println(clone.sub.getSName());
System.out.println("=================hashcode================");
System.out.println(parent.hashCode() == clone.hashCode());
System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
}
}
輸出:
==============parent===================
166239592
pName
991505714
sName
================clone===================
385242642
pName
991505714
sName
=================hashcode================
false
true
- 深克隆
@Getter
@AllArgsConstructor
public class Sub implements Cloneable {
private String sName;
@Override
protected Sub clone() {
Sub sub = null;
try {
sub = (Sub) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sub;
}
}
@AllArgsConstructor
public class Parent implements Cloneable {
private String pName;
private Sub sub;
@Override
protected Object clone() {
Parent parent = null;
try {
parent = (Parent) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
assert parent != null;
parent.sub = sub.clone();
return parent;
}
public static void main(String[] args) {
Sub sub = new Sub("sName");
Parent parent = new Parent("pName", sub);
Parent clone = (Parent) parent.clone();
System.out.println("==============parent===================");
System.out.println(parent.hashCode());
System.out.println(parent.pName);
System.out.println(parent.sub.hashCode());
System.out.println(parent.sub.getSName());
System.out.println("================clone===================");
System.out.println(clone.hashCode());
System.out.println(clone.pName);
System.out.println(clone.sub.hashCode());
System.out.println(clone.sub.getSName());
System.out.println("=================hashcode================");
System.out.println(parent.hashCode() == clone.hashCode());
System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
}
}
輸出:
==============parent===================
166239592
pName
991505714
sName
================clone===================
385242642
pName
824009085
sName
=================hashcode================
false
false
實現(xiàn)Serializable接口蕴侧,深克隆
public class CloneUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//寫入字節(jié)流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配內(nèi)存择同,寫入原始對象,生成新對象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新對象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
@Getter
@AllArgsConstructor
public class Sub implements Serializable {
private String sName;
}
@AllArgsConstructor
public class Parent implements Serializable {
private String pName;
private Sub sub;
public static void main(String[] args) {
Sub sub = new Sub("sName");
Parent parent = new Parent("pName", sub);
Parent clone = CloneUtils.clone(parent);
System.out.println("==============parent===================");
System.out.println(parent.hashCode());
System.out.println(parent.pName);
System.out.println(parent.sub.hashCode());
System.out.println(parent.sub.getSName());
System.out.println("================clone===================");
System.out.println(clone.hashCode());
System.out.println(clone.pName);
System.out.println(clone.sub.hashCode());
System.out.println(clone.sub.getSName());
System.out.println("=================hashcode================");
System.out.println(parent.hashCode() == clone.hashCode());
System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
}
}
輸出:
==============parent===================
708049632
pName
716083600
sName
================clone===================
1791930789
pName
762152757
sName
=================hashcode================
false
false
擴展
- 標識接口 : Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單净宵,它們都是空接口敲才,這種空接口也稱為標識接口,標識接口中沒有任何方法的定義塘娶,其作用是告訴JRE這些接口的實現(xiàn)類是否具有某個功能归斤,如是否支持克隆痊夭、是否支持序列化等刁岸。