java深克隆和淺克隆 基本概念 1. 被覆制對象的所有變數都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所拷貝的對象,而不複製它所引用的對象。 1. 被覆制對象的所有變數都含有與原來的對象相同的值,除去那些引用其他對象的變數。那些引用其他對象的變數將 ...
java深克隆和淺克隆
基本概念
淺複製(淺克隆)
被覆制對象的所有變數都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所拷貝的對象,而不複製它所引用的對象。
深複製(深克隆)
被覆制對象的所有變數都含有與原來的對象相同的值,除去那些引用其他對象的變數。那些引用其他對象的變數將指向被覆制過的新對象,而不再是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。
實現java深複製和淺複製的最關鍵的就是要實現Object中的clone()方法
。
如何使用clone()方法
首先我們來看一下Cloneable介面:
官方解釋:
1:實現此介面則可以使用java.lang.Object 的clone()方法,否則會拋出CloneNotSupportedException 異常
2:實現此介面的類應該使用公共方法覆蓋clone方法
3:此介面並不包含clone 方法,所以實現此介面並不能克隆對象,這隻是一個前提,還需覆蓋上面所講的clone方法。
public interface Cloneable {
}
看看Object裡面的Clone()方法:
- clone()方法返回的是Object類型,所以必須強制轉換得到克隆後的類型
- clone()方法是一個native方法,而native的效率遠遠高於非native方法,
- 可以發現clone方法被一個Protected修飾,所以可以知道必須繼承Object類才能使用,而Object類是所有類的基類,也就是說所有的類都可以使用clone方法
protected native Object clone() throws CloneNotSupportedException;
小試牛刀:
public class Person {
public void testClone(){
super.clone(); // 報錯了
}
}
事實卻是clone()方法報錯了,那麼肯定奇怪了,既然Object是一切類的基類,並且clone的方法是Protected的,那應該是可以通過super.clone()方法去調用的,然而事實卻是會拋出CloneNotSupportedException異常
, 官方解釋
如下:
- 對象的類不支持Cloneable介面
- 覆蓋方法的子類也可以拋出此異常表示無法克隆實例。
所以我們更改代碼如下:
public class Person implements Cloneable{
public void testClone(){
try {
super.clone();
System.out.println("克隆成功");
} catch (CloneNotSupportedException e) {
System.out.println("克隆失敗");
e.printStackTrace();
}
}
public static void main(String[] args) {
Person p = new Person();
p.testClone();
}
}
要註意,必須將克隆方法寫在try-catch塊中,因為clone方法會把異常拋出,當然程式也要求我們try-catch。
java.lang.object規範中對clone方法的約定
對任何的對象x,都有x.clone() !=x 因為克隆對象與原對象不是同一個對象
對任何的對象x,都有x.clone().getClass()= =x.getClass()//克隆對象與原對象的類型一樣
如果對象x的equals()方法定義恰當,那麼x.clone().equals(x)應該成立
對於以上三點要註意,這3項約定並沒有強制執行,所以如果用戶不遵循此約定,那麼將會構造出不正確的克隆對象,所以根據effective java的建議:
謹慎的使用clone方法,或者儘量避免使用。
淺複製實例
對象中全部是基本類型
public class Teacher implements Cloneable{
private String name;
private int age;
public Teacher(String name, int age){
this.name = name;
this.age = age;
}
// 覆蓋
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 客戶端測試
public class test {
Teacher origin = new Teacher("tony", 11);
System.out.println(origin.getName());
Teacher clone = (Teacher) origin.clone();
clone.setName("clone");
System.out.println(origin.getName());
System.out.println(clone.getName());
}
結果:
tony
tony
clone
從運行結果和圖上可以知道,克隆後的值變數會開闢新的記憶體地址,克隆對象修改值不會影響原來對象。
對象中含有引用類型
public class Teacher implements Cloneable{
private String name;
private int age;
private Student student;
public Teacher(String name, int age, Student student){
this.name = name;
this.age = age;
this.student = student;
}
// 覆蓋
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
// 學生類
public class Student {
private String name;
private int age;
public Student(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 客戶端測試
public class test {
public static void main(String[] args) {
Student student = new Student("學生1" ,11);
Teacher origin = new Teacher("老師", 11, student);;
Teacher clone = (Teacher) origin.clone();
System.out.println("比較克隆後的引用對象");
System.out.println(origin.getStudent().getClass() == clone.getStudent().getClass());
Student student2 = new Student("學生2", 12);
clone.setStudent(student2);
System.out.println("克隆後,比較克隆對象改變引用");
System.out.println(origin.getStudent().getClass() == clone.getStudent().getClass());
}
}
運行結果:
比較克隆後的引用對象
true
克隆後,比較克隆對象改變引用
true
如圖可知,引用類型只會存在一份記憶體地址,執行object的clone方法拷貝的也是引用的複製(這部分的記憶體空間不一樣,)但是引用指向的記憶體空間是一樣的,原對象修改引用變數或者淺拷貝對象修改引用變數都會引起雙方的變化
重點:綜上兩個方面可以知道,Object的clone方法是屬於淺拷貝,基本變數類型會複製相同值,而引用變數類型也是會複製相同的引用。
深複製實例
從上面的淺拷貝可以知道,對於引用的變數只會拷貝引用指向的地址,也就是指向同一個記憶體地址,但是很多情況下我們需要的是下麵圖的效果:
深拷貝實現的是對所有可變(沒有被final修飾的引用變數)
引用類型的成員變數都開闢記憶體空間所以一般深拷貝對於淺拷貝來說是比較耗費時間和記憶體開銷的。
深拷貝的兩種方式:
重寫clone方法實現深拷貝
學生類:
public class Student implements Cloneable {
private String name;
private int age;
public Student(String name, int age){
this.name = name;
this.age = age;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
老師類:
public class Teacher implements Cloneable{
private String name;
private int age;
private Student student;
public Teacher(String name, int age, Student student){
this.name = name;
this.age = age;
this.student = student;
}
// 覆蓋
@Override
public Object clone() {
Teacher t = null;
try {
t = (Teacher) super.clone();
t.student = (Student)student.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return t;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
測試端:
public class test {
public static void main(String[] args) {
Student s = new Student("學生1", 11);
Teacher origin = new Teacher("老師原對象", 23, s);
System.out.println("克隆前的學生姓名:" + origin.getStudent().getName());
Teacher clone = (Teacher) origin.clone();
// 更改克隆後的學生信息 更改了姓名
clone.getStudent().setName("我是克隆對象更改後的學生2");
System.out.println("克隆後的學生姓名:" + clone.getStudent().getName());
}
}
運行結果:
克隆前的學生姓名:學生1
克隆後的學生姓名:我是克隆對象更改後的學生2
序列化實現深克隆
我們發現上面通過object的clone方法去實現深克隆十分麻煩, 因此引出了另外一種方式:序列化實現深克隆
。
概念:
- 序列化:把對象寫到流里
- 反序列化:把對象從流中讀出來
在Java語言里深複製一個對象,常常可以先使對象實現Serializable介面,然後把對象(實際上只是對象的一個拷貝)寫到一個流里,再從流里讀出來,便可以重建對象。
註意:
寫在流里的是對象的一個拷貝,而原對象仍然存在於JVM裡面
。對象以及對象內部所有引用到的對象都是可序列化的
如果不想序列化,則需要使用transient來修飾
案例:
Teacher:
public class Teacher implements Serializable{
private String name;
private int age;
private Student student;
public Teacher(String name, int age, Student student){
this.name = name;
this.age = age;
this.student = student;
}
// 深克隆
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();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
Student:
public class Student implements Serializable {
private String name;
private int age;
public Student(String name, int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
client:
public class test {
public static void main(String[] args) {
try {
Student s = new Student("學生1", 11);
Teacher origin = new Teacher("老師原對象", 23, s);
System.out.println("克隆前的學生姓名:" + origin.getStudent().getName());
Teacher clone = (Teacher) origin.deepClone();
// 更改克隆後的d學生信息 更改了姓名
clone.getStudent().setName("我是克隆對象更改後的學生2");
System.out.println("克隆後的學生姓名:" + clone.getStudent().getName());
}catch (Exception e){
e.printStackTrace();
}
}
}
當然這些工作都有現成的輪子了,藉助於Apache Commons可以直接實現:
- 淺克隆:BeanUtils.cloneBean(Object obj);
- 深克隆:SerializationUtils.clone(T object);
最後探討
在java中為什麼實現了Cloneable介面,就可以調用Object中的Clone方法
參考以下回答:
https://www.zhihu.com/question/52490586
號外號外:
- 如果有小伙伴覺得我寫的不錯的話可以關註一下我的博客哦
- 可以關註下方我的公眾號
java架構師小密圈
,回覆1:獲取2Tjava架構師必備乾貨 ,另外:小伙伴可以回覆任意想學的技術,可以免費幫你搜尋
,其實我們還需要學很多!!!!!!
- 還會分享一些賺錢理財的小套路哦,歡迎大家來支持,一起學習成長,程式員不僅僅是搬瓦工!
公眾號:分享系列好文章
交流群:一起奔著java架構師努力