1、拷貝與深拷貝
- 對象的拷貝,就是將原對象中的屬性值拷貝到一個同類型(一般來說)的對象中去;
深拷貝是指在拷貝原對象當(dāng)中屬性值的同時,如果屬性是引用類型,那么將該引用類型指向一塊新的內(nèi)存,避免新舊對象引用類型數(shù)據(jù)修改而相互影響
2、java的Cloneable接口
java對于所有的對象父類Object,提供了一個protected的clone方法,如果我們自己的類需要提供clone功能,需要實現(xiàn)一個空接口:Serializable并重寫Object的clone方法,否則會報:CloneNotSupportedException
示例代碼
public class MyInt implements Cloneable {
public int i;
public MyInt(int i) {
this.i = i;
}
/**
* 在jdk1.5之前,這里只能返回Object
* 由于jdk1.5之后引入的泛型,所以這里可以返回最終類型
*
* @return
* @throws CloneNotSupportedException
*/
@Override
protected MyInt clone() throws CloneNotSupportedException {
return (MyInt) super.clone();
}
@Override
public String toString() {
return "MyInt{" +
"i=" + i +
'}';
}
}
3、深拷貝的實現(xiàn)
1. 實現(xiàn)Cloneable接口并重寫clone()方法
對于引用
需要正確的重寫引用對象的clone()方法
public class MyInteger implements Cloneable {
MyInt myInt = new MyInt(0);
public MyInteger(int i) {
myInt = new MyInt(i);
}
@Override
protected MyInteger clone() throws CloneNotSupportedException {
return (MyInteger) super.clone();
}
@Override
public String toString() {
return "MyInteger{" +
"myInt=" + myInt +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
MyInteger myInteger = new MyInteger(1);
MyInteger myInteger1_clone = myInteger.clone();
myInteger.myInt.i = 15;
System.out.println(myInteger);
System.out.println(myInteger1_clone);
}
}
上面這段代碼,輸出結(jié)果為:
MyInteger{myInt=MyInt{i=15}}
MyInteger{myInt=MyInt{i=15}}
為何調(diào)用clone后創(chuàng)建的對象的值,隨著原對象的值的改變而改變了?
由于在MyInteger的clone()方法中,只是簡單的調(diào)用了super.clone()創(chuàng)建了一個新對象返回,這樣,myInteger.myInt與myInteger1_clone.myInt指向的是同一個對象

所以修改myInteger.myInt.i屬性的值,自然會導(dǎo)致myInteger1_clone.myInt.i的值隨著一起改變
將上述代碼略微修改:
public class MyInteger implements Cloneable {
MyInt myInt = new MyInt(0);
public MyInteger(int i) {
myInt = new MyInt(i);
}
@Override
protected MyInteger clone() throws CloneNotSupportedException {
MyInteger clone = (MyInteger) super.clone();
clone.myInt = new MyInt(this.myInt.i);
return clone;
}
@Override
public String toString() {
return "MyInteger{" +
"myInt=" + myInt +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
MyInteger myInteger = new MyInteger(1);
MyInteger myInteger1_clone = myInteger.clone();
myInteger.myInt.i = 15;
System.out.println(myInteger);
System.out.println(myInteger1_clone);
}
}
程序輸出:
MyInteger{myInt=MyInt{i=15}}
MyInteger{myInt=MyInt{i=1}}
debug:

OJBK
需要循環(huán)調(diào)用引用對象的正確的clone()方法,一直到基本數(shù)據(jù)類型或者String
數(shù)組對象
一維數(shù)組
對于原始數(shù)據(jù)類型或者String的一維數(shù)組,可以直接調(diào)用數(shù)組的clone()方法來完成對象的copy
public static void main(String[] args) {
String[] strs = {"aa", "bb", "cc"};
String[] strs_clone = strs.clone();
strs[1] = "dd";
System.out.println(Arrays.toString(strs));
System.out.println(Arrays.toString(strs_clone));
}
程序輸出:
[aa, dd, cc]
[aa, bb, cc]
OK,沒有問題,但是對于引用類型的數(shù)組,這樣類似的修改會使原對象和新對象的屬性同時發(fā)生變更
public class ArraysClone implements Cloneable {
MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};
@Override
protected ArraysClone clone() throws CloneNotSupportedException {
return (ArraysClone) super.clone();
}
@Override
public String toString() {
return "ArraysClone{" +
"myInts=" + Arrays.toString(myInts) +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
ArraysClone ac = new ArraysClone();
ArraysClone ac_clone = ac.clone();
ac.myInts[1].i = 5;
System.out.println(ac);
System.out.println(ac_clone);
}
}
程序輸出
[MyInt{i=1}, MyInt{i=5}, MyInt{i=3}]
[MyInt{i=1}, MyInt{i=5}, MyInt{i=3}]
對于引用類型的數(shù)組,需要在持有數(shù)組引用的對象的clone()方法中,對于新數(shù)組對象進行遞歸創(chuàng)建新對象并拷貝原對象的值----可以調(diào)用正確的clone()
public class ArraysClone implements Cloneable {
MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};
@Override
protected ArraysClone clone() throws CloneNotSupportedException {
ArraysClone clone = (ArraysClone) super.clone();
clone.myInts = new MyInt[this.myInts.length];
for (int i = 0; i < this.myInts.length; i++) {
clone.myInts[i] = this.myInts[i].clone();
}
return clone;
}
@Override
public String toString() {
return "ArraysClone{" +
"myInts=" + Arrays.toString(myInts) +
'}';
}
public static void main(String[] args) throws CloneNotSupportedException {
ArraysClone ac = new ArraysClone();
ArraysClone ac_clone = ac.clone();
ac.myInts[1].i = 5;
System.out.println(ac);
System.out.println(ac_clone);
}
}
二維數(shù)組
public class MultiArraysClone implements Cloneable {
int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
@Override
public String toString() {
return "MultiArraysClone{" +
"intss=" + Arrays.deepToString(intss) +
'}';
}
@Override
protected MultiArraysClone clone() throws CloneNotSupportedException {
MultiArraysClone clone = (MultiArraysClone) super.clone();
clone.intss = this.intss.clone();
return clone;
}
public static void main(String[] args) throws CloneNotSupportedException {
MultiArraysClone multiArraysClone = new MultiArraysClone();
MultiArraysClone multiArraysClone_clone = multiArraysClone.clone();
multiArraysClone.intss[1][0] = 99;
multiArraysClone.intss[1][1] = 99;
System.out.println(multiArraysClone);
System.out.println(multiArraysClone_clone);
}
}
程序輸出
MultiArraysClone{intss=[[10, 11], [99, 99], [30, 31]]}
MultiArraysClone{intss=[[10, 11], [99, 99], [30, 31]]}
為什么對于基本數(shù)據(jù)類型,二維數(shù)組和一維數(shù)組的clone后結(jié)果不一致呢
下面是在我電腦上對于二維數(shù)組clone后的debug信息

通過對象信息可以清晰看到,對于二維數(shù)組執(zhí)行clone(),數(shù)組自身引用已經(jīng)改變,但是數(shù)組的第一個維度的引用依舊指向原數(shù)組的引用
由于java并不存在真正意義上的二維數(shù)組,二維數(shù)組的實現(xiàn)是一個以一維數(shù)組為元素的一維數(shù)組,所以,對于二維數(shù)組的clone()行為的理解,與上面對象的一維數(shù)組的clone()方式是一致的
其他更高維度的數(shù)組同:如果需要對高維數(shù)組執(zhí)行深拷貝,需要最終遞歸對于構(gòu)成這個高維數(shù)組的每個一維數(shù)組執(zhí)行深拷貝。
2. 拷貝構(gòu)造器和拷貝工廠
拷貝構(gòu)造器是一個參數(shù)為該類對象的構(gòu)造器。
拷貝工廠是一個類似于拷貝構(gòu)造器的靜態(tài)工廠方法。
public class Foo {
MyInt[] myInts = {new MyInt(1), new MyInt(2), new MyInt(3)};
public Foo() {
}
public Foo(Foo src) {
for (int i=0; i<src.myInts.length; i++) {
this.myInts[i] = new MyInt(src.myInts[i].i);
}
}
public static Foo deepCopy(Foo src) {
Foo dest = new Foo();
for (int i=0; i<src.myInts.length; i++) {
dest.myInts[i] = new MyInt(src.myInts[i].i);
}
return dest;
}
@Override
public String toString() {
return "Foo{" +
"myInts=" + Arrays.toString(myInts) +
'}';
}
public static void main(String[] args) {
Foo src = new Foo();
Foo constructor_clone = new Foo(src);
Foo factory_clone = Foo.deepCopy(src);
src.myInts[1].i = 99;
System.out.println(src);
System.out.println(constructor_clone);
System.out.println(factory_clone);
}
}
其本質(zhì)實現(xiàn)與clon()一致,但是其好處是:
(引自effective java --- 謹(jǐn)慎地覆蓋clone)
拷貝構(gòu)造器的做法,及其靜態(tài)工廠方法的變形,都比Cloneable/clone方法具有更多的優(yōu)勢,它們不依賴于某
一種很有風(fēng)險的、語言之外的對象創(chuàng)建機制;它們不要求遵守尚未制定好文檔的規(guī)范;它們不會與final域的
正常使用發(fā)生沖突;它們不會拋出不必要的受檢異常(check exception);他們不需要進行類型轉(zhuǎn)換
3. 序列化與反序列化
這個先上的代碼,第一次看到是在這種實現(xiàn)是在stackOverFlow,現(xiàn)在已經(jīng)遍地都是了!
當(dāng)然,具體原創(chuàng)是誰我不知道。
public static <T extends Serializable> T deepCopy(T src) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(src);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
@SuppressWarnings("unchecked")
T dest = (T) in.readObject();
return dest;
}
通過序列化與反序列化操作完成deepcopy動作
優(yōu)點:不需要覆蓋clone(),不需要單獨處理每一個引用的clone行為
缺點:
- 需要每一個引用對象實現(xiàn)Serializable接口
不實現(xiàn)Serializable會報java.io.NotSerializableException
- 需要深拷貝的屬性不能是transient,或者在readObject和writeObject中有對于transient屬性的正確處理
- readObject和writeObject對于序列化過程的控制影響
不干預(yù)transient的序列化過程:
public class DeepCloneBySerizalizable implements Serializable {
int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
// 由于不特殊處理,transient不會序列化,所以會在clone對象中打印null
transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};
@Override
public String toString() {
return "DeepCloneBySerizalizable{" +
"\nintss=" + Arrays.deepToString(intss) + ", " +
"\nintss1=" + Arrays.deepToString(intss1) + ", " +
"\n}\n\n";
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);
dcbs.intss[1][0] = 99;
dcbs.intss[1][1] = 99;
dcbs.intss1[1][0] = 999;
dcbs.intss1[1][1] = 999;
System.out.println(dcbs);
System.out.println("-------------------------------------------\n\n");
System.out.println(dcbs_clone);
}
}
程序輸出:
DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]],
intss1=[[10, 11], [999, 999], [30, 31]],
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]],
intss1=null,
}
用readObject和writeObject干預(yù)對于transient對象的序列化與反序列化過程:
public class DeepCloneBySerizalizable implements Serializable {
int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
// 由于不特殊處理,transient不會序列化,所以會在clone對象中打印null
transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};
@Override
public String toString() {
return "DeepCloneBySerizalizable{" +
"\nintss=" + Arrays.deepToString(intss) + ", " +
"\nintss1=" + Arrays.deepToString(intss1) + ", " +
"\n}";
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
//示例代碼,別糾結(jié)細節(jié)。
oos.writeInt(intss1.length);
for (int i = 0; i < intss1.length; i++) {
oos.writeObject(intss1[i]);
}
}
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
ois.defaultReadObject();
//示例代碼,別糾結(jié)細節(jié)。
int length = ois.readInt();
this.intss1 = new int[3][2];
for (int i = 0; i < length; i++) {
this.intss1[i] = (int[]) ois.readObject();
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);
dcbs.intss[1][0] = 99;
dcbs.intss[1][1] = 99;
dcbs.intss1[1][0] = 999;
dcbs.intss1[1][1] = 999;
System.out.println(dcbs);
System.out.println("-------------------------------------------");
System.out.println(dcbs_clone);
}
}
程序輸出:
DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]],
intss1=[[10, 11], [999, 999], [30, 31]],
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]],
intss1=[[10, 11], [20, 21], [30, 31]],
}
- readResolve對于反序列化對象的影響
public class DeepCloneBySerizalizable implements Serializable {
int[][] intss = {{10, 11}, {20, 21}, {30, 31}};
// 由于不特殊處理,transient不會序列化,所以會在clone對象中打印null
transient int[][] intss1 = {{10, 11}, {20, 21}, {30, 31}};
@Override
public String toString() {
return "DeepCloneBySerizalizable{" +
"\nintss=" + Arrays.deepToString(intss) + ", " +
"\nintss1=" + Arrays.deepToString(intss1) + ", " +
"\n}";
}
private Object readResolve() {
//示例代碼,別糾結(jié)細節(jié)。
DeepCloneBySerizalizable deepCloneBySerizalizable = new DeepCloneBySerizalizable();
deepCloneBySerizalizable.intss1[1][1] = -123;
return deepCloneBySerizalizable;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
DeepCloneBySerizalizable dcbs = new DeepCloneBySerizalizable();
DeepCloneBySerizalizable dcbs_clone = deepCopy(dcbs);
dcbs.intss[1][0] = 99;
dcbs.intss[1][1] = 99;
dcbs.intss1[1][0] = 999;
dcbs.intss1[1][1] = 999;
System.out.println(dcbs);
System.out.println("-------------------------------------------");
System.out.println(dcbs_clone);
}
}
輸出:
DeepCloneBySerizalizable{
intss=[[10, 11], [99, 99], [30, 31]],
intss1=[[10, 11], [999, 999], [30, 31]],
}
-------------------------------------------
DeepCloneBySerizalizable{
intss=[[10, 11], [20, 21], [30, 31]],
intss1=[[10, 11], [20, -123], [30, 31]],
}
由于可以通過readResolve()方法修改反序列化后對象屬性,所以,如果進行深拷貝涉及的對象有readResolve(),那么需要注意了,如果有特殊值修改,用序列化與反序列化過程來實現(xiàn)深拷貝將不可用
- static變量正常情況下是不參與序列化與反序列化的,雖然大部分時候這個不影響結(jié)果。
這個算課后作業(yè)? 參見《深入理解java虛擬機》,提醒static變量放在哪?
- etc.