1、概述
拷貝的一個經典的使用場景:當前對象要傳給其他多個方法使用,如果該對象在某一個方法中被修改,那麼這個修改會影響到其他方法。 如果要避免這種影響,就需要給每一個方法都傳入一個當前對象的拷貝。
深與淺拷貝的區別就在於對複雜對象的處理:對於基本類型,淺拷貝、深拷貝都是拷貝的值;對於引用類型淺拷貝的是對象的引用。而深拷貝則是直接新建一個對象實例。
注意淺拷貝中的複雜引用以及簡單引用:對於簡單引用,拷貝後的對象指向新的地址不會影響到原對象。複雜引用,拷貝後的對象將內部的對象指向新的地址,會影響到原對象對應的內部對象。這裏一定理解下,抓住“地址”指向去理解就好了。
2、代碼實現
(1)淺拷貝實現方式:類實現Cloneable接口,並且重寫clone()方法。詳細見下面的代碼
(2)深拷貝:主要是針對類內部的複雜引用變量。兩種方式:1)複雜引用也實現Cloneable,並重寫clone()方法,然後在父對象重寫的clone方法中將該複雜引用指向克隆後的引用 2)直接將需要克隆的對象進行序列化,然後反序列化就可以得到一個深拷貝的對象。
當然這些工作都有現成的輪子了,藉助於Apache Commons工具類可以直接實現:
- 淺克隆:BeanUtils.cloneBean(Object obj);
- 深克隆:SerializationUtils.clone(T object);
當然可以直接使用
2.1淺拷貝
父子類
1 @Data
2 @Builder
3 class Father implements Cloneable {
4 Long age;
5 StringBuilder name;
6 Son son;
7
8 @Override
9 public Object clone() {
10 //淺拷貝
11 try {
12 return super.clone();
13 } catch (CloneNotSupportedException e) {
14 e.printStackTrace();
15 return null;
16 }
17 }
18 }
19
20 @Data
21 @Builder
22 class Son {
23 Long age;
24 String name;
25 int grade;
26 }
初始賦值
1 public static Son son = Son.builder().age(new Long(30L)).name("大兒子").grade(100).build();
2 public static Father father = Father.builder().age(new Long(50L)).name("爸爸").son(son).build();
淺拷貝示例:
private static void shallowClone() {
System.out.println("父親年齡:" + father.getAge());
System.out.println("父親姓名:" + father.getName());
System.out.println("兒子年齡:" + father.getSon().getAge());
System.out.println("兒子姓名:" + father.getSon().getName());
System.out.println("兒子分數:" + father.getSon().getGrade());
//開始克隆
Father fatherInLaw = (Father) father.clone();
fatherInLaw.getSon().setAge(10L);
fatherInLaw.getSon().setGrade(0);
fatherInLaw.getSon().setName("繼子");
fatherInLaw.setAge(new Long(80L));
fatherInLaw.setName("繼父");
//修改後結果
System.out.println("==========淺拷貝後===========");
System.out.println("父親年齡:" + father.getAge());
System.out.println("父親姓名:" + father.getName());
System.out.println("兒子年齡:" + father.getSon().getAge());
System.out.println("兒子姓名:" + father.getSon().getName());
System.out.println("兒子分數:" + father.getSon().getGrade());
}
結果輸出
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
==========淺拷貝後===========
父親年齡:50
父親姓名:爸爸
兒子年齡:10
兒子姓名:繼子
兒子分數:0
2.2.深拷貝
(1)子引用重寫clone方法,並且在父類中改變子引用的指向
父子類
@Data
@Builder
class Father implements Cloneable {
Long age;
StringBuilder name;
Son son;
@Override
public Object clone() {
Father father = null;
//淺拷貝
try {
father = (Father) super.clone();
father.son = (Son) son.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
return father;
}
}
@Data
@Builder
class Son implements Cloneable {
Long age;
String name;
int grade;
@Override
public Object clone() {
//拷貝
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
View Code
賦初值
1 public static Son son = Son.builder().age(new Long(30L)).name("大兒子").grade(100).build();
2 public static Father father = Father.builder().age(new Long(50)).name(new StringBuilder("爸爸")).son(son).build();
深拷貝示例:
//深拷貝
private static void deepClone() {
System.out.println("父親年齡:" + father.getAge());
System.out.println("父親姓名:" + father.getName());
System.out.println("兒子年齡:" + father.getSon().getAge());
System.out.println("兒子姓名:" + father.getSon().getName());
System.out.println("兒子分數:" + father.getSon().getGrade());
//開始克隆
Father fatherInLaw = (Father) father.clone();
fatherInLaw.getSon().setAge(10L);
fatherInLaw.getSon().setGrade(0);
fatherInLaw.getSon().setName("繼子");
fatherInLaw.setAge(new Long(80L));
fatherInLaw.setName(new StringBuilder("繼父"));
//修改前
System.out.println("==========淺拷貝後===========");
System.out.println("父親年齡:" + father.getAge());
System.out.println("父親姓名:" + father.getName());
System.out.println("兒子年齡:" + father.getSon().getAge());
System.out.println("兒子姓名:" + father.getSon().getName());
System.out.println("兒子分數:" + father.getSon().getGrade());
}
結果
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
==========淺拷貝後===========
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
(2)序列化方式
父子類
@Data
@Builder
class Father implements Serializable {
Long age;
StringBuilder name;
Son son;
public Object deepClone() throws IOException, ClassNotFoundException {
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
@Data
@Builder
class Son implements Serializable {
Long age;
String name;
int grade;
}
View Code
實現方法
//深拷貝
private static void deepCloneV2() throws IOException, ClassNotFoundException {
System.out.println("父親年齡:" + father.getAge());
System.out.println("父親姓名:" + father.getName());
System.out.println("兒子年齡:" + father.getSon().getAge());
System.out.println("兒子姓名:" + father.getSon().getName());
System.out.println("兒子分數:" + father.getSon().getGrade());
//開始克隆
Father fatherInLaw = (Father) father.deepClone();
fatherInLaw.getSon().setAge(10L);
fatherInLaw.getSon().setGrade(0);
fatherInLaw.getSon().setName("繼子");
fatherInLaw.setAge(new Long(80L));
fatherInLaw.setName(new StringBuilder("繼父"));
//修改前
System.out.println("==========淺拷貝後===========");
System.out.println("父親年齡:" + father.getAge());
System.out.println("父親姓名:" + father.getName());
System.out.println("兒子年齡:" + father.getSon().getAge());
System.out.println("兒子姓名:" + father.getSon().getName());
System.out.println("兒子分數:" + father.getSon().getGrade());
}
結果:
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
==========淺拷貝後===========
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
3、使用Apach Commons
3.1實現深拷貝,需要實現序列化接口,SerializationUtils.clone(T object);
父子類
@Data
@Builder
class Father implements Serializable{
Long age;
StringBuilder name;
Son son;
}
@Data
@Builder
class Son implements Serializable{
Long age;
String name;
int grade;
}
View Code
實現方法
//深拷貝
private static void deepCloneCommons() throws IOException, ClassNotFoundException {
System.out.println("父親年齡:" + father.getAge());
System.out.println("父親姓名:" + father.getName());
System.out.println("兒子年齡:" + father.getSon().getAge());
System.out.println("兒子姓名:" + father.getSon().getName());
System.out.println("兒子分數:" + father.getSon().getGrade());
//開始克隆
Father fatherInLaw = (Father) SerializationUtils.clone(father);
fatherInLaw.getSon().setAge(10L);
fatherInLaw.getSon().setGrade(0);
fatherInLaw.getSon().setName("繼子");
fatherInLaw.setAge(new Long(80L));
fatherInLaw.setName(new StringBuilder("繼父"));
//修改前
System.out.println("==========淺拷貝後===========");
System.out.println("父親年齡:" + father.getAge());
System.out.println("父親姓名:" + father.getName());
System.out.println("兒子年齡:" + father.getSon().getAge());
System.out.println("兒子姓名:" + father.getSon().getName());
System.out.println("兒子分數:" + father.getSon().getGrade());
}
View Code
結果
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
==========深拷貝後===========
父親年齡:50
父親姓名:爸爸
兒子年齡:30
兒子姓名:大兒子
兒子分數:100
3.2使用工具類實現深拷貝,BeanUtils.copyProperties(Object dest, Object orig)直接調用即可實現淺拷貝,BeanUtils.cloneBean(Object obj)最終也是調用的copyProperties方法。