Cloneable接口的作用與深入理解深度克隆與淺度克隆

cloneable其實就是一個標記接口,只有實現這個接口後,然後在類中重寫Object中的clone方法,然後通過類調用clone方法才能克隆成功,如果不實現這個接口,則會拋出CloneNotSupportedException(克隆不被支持)異常。Object中clone方法:

Cloneable接口的作用與深入理解深度克隆與淺度克隆

這裡有一個疑問,Object中的clone方法是一個空的方法,那麼他是如何判斷類是否實現了cloneable接口呢?

原因在於這個方法中有一個native關鍵字修飾。

native修飾的方法都是空的方法,但是這些方法都是有實現體的(這裡也就間接說明了native關鍵字不能與abstract同時使用。因為abstract修飾的方法與java的接口中的方法類似,他顯式的說明了修飾的方法,在當前是沒有實現體的,abstract的方法的實現體都由子類重寫),只不過native方法調用的實現體,都是非java代碼編寫的(例如:調用的是在jvm中編寫的C的接口),每一個native方法在jvm中都有一個同名的實現體,native方法在邏輯上的判斷都是由實現體實現的,另外這種native修飾的方法對返回類型,異常控制等都沒有約束。

由此可見,這裡判斷是否實現cloneable接口,是在調用jvm中的實現體時進行判斷的。

深入理解深度克隆與淺度克隆

首先,在java中創建對象的方式有四種:

一種是new,通過new關鍵字在堆中為對象開闢空間,在執行new時,首先會看所要創建的對象的類型,知道了類型,才能知道需 要給這個對象分配多大的內存區域,分配內存後,調用對象的構造函數,填充對象中各個變量的值,將對象初始化,然後通過構造方法返回對象的地址;

另一種是clone,clone也是首先分配內存,這裡分配的內存與調用clone方法對象的內存相同,然後將源對象中各個變量的值,填充到新的對象中,填充完成後,clone方法返回一個新的地址,這個新地址的對象與源對象相同,只是地址不同。

另外還有輸入輸出流,反射構造對象等

下面通過幾個例子來解析下淺度克隆與深度克隆的區別:

淺度克隆測試:

首先定義一個學生類

public class Student{

private String name; //姓名

private int age; //年齡

private StringBuffer sex; //性別

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 StringBuffer getSex() {

return sex;

}

public void setSex(StringBuffer sex) {

this.sex = sex;

}

@Override

public String toString() {

return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";

}

}

其次定義一個學校類,類中重寫clone方法

public class School implements Cloneable{

private String schoolName; //學校名稱

private int stuNums; //學校人數

private Student stu; //一個學生

public String getSchoolName() {

return schoolName;

}

public void setSchoolName(String schoolName) {

this.schoolName = schoolName;

}

public int getStuNums() {

return stuNums;

}

public void setStuNums(int stuNums) {

this.stuNums = stuNums;

}

public Student getStu() {

return stu;

}

public void setStu(Student stu) {

this.stu = stu;

}

@Override

protected School clone() throws CloneNotSupportedException {

// TODO Auto-generated method stub

return (School)super.clone();;

}

@Override

public String toString() {

return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]";

}

}

最後定義一個main類來測試一下:

public static void main(String[] args) throws CloneNotSupportedException {

School s1 = new School();

s1.setSchoolName("實驗小學");

s1.setStuNums(100);

Student stu1 = new Student();

stu1.setAge(20);

stu1.setName("zhangsan");

stu1.setSex(new StringBuffer("男"));

s1.setStu(stu1);

System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode());

School s2 = s1.clone(); //調用重寫的clone方法,clone出一個新的school---s2

System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());

測試結果:

Cloneable接口的作用與深入理解深度克隆與淺度克隆

可以看出s1與s2的hashcode不同,也就是說clone方法並不是把s1的引用賦予s2,而是在堆中重新開闢了一塊空間,將s1複製過去,將新的地址返回給s2。

但是s1中stu的hashcode與s2中stu的hashcode相同,也就是這兩個指向了同一個對象,修改s2中的stu會造成s1中stu數據的改變。但是修改s2中的基本數據類型與Stirng類型時,不會造成s1中數據的改變,基本數據類型例如int,在clone的時候會重新開闢一個四個字節的大小的空間,將其賦值。而String則由於String變量的唯一性,如果在s2中改變了String類型的值,則會生成一個新的String對象,對之前的沒有影響。 這就是淺度克隆。

如何實現深度clone?(下面時第一種方法,另外使用序列化將student變成流,輸入再輸出也可以)

首先需要讓student重寫clone方法,實現cloneable接口

public class Student implements Cloneable{

private String name;

private int age;

private StringBuffer sex;

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 StringBuffer getSex() {

return sex;

}

public void setSex(StringBuffer sex) {

this.sex = sex;

}

@Override

public String toString() {

return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";

}

@Override

protected Student clone() throws CloneNotSupportedException {

// TODO Auto-generated method stub

return (Student)super.clone();

}

}

然後,在school的clone方法中將school中的stu對象手動clone一下。

@Override

protected School clone() throws CloneNotSupportedException {

// TODO Auto-generated method stub

School s = null;

s = (School)super.clone();

s.stu = stu.clone();

return s;

}

再次執行main方法查看結果:

public class Main {

public static void main(String[] args) throws CloneNotSupportedException {

School s1 = new School();

s1.setSchoolName("實驗小學");

s1.setStuNums(100);

Student stu1 = new Student();

stu1.setAge(20);

stu1.setName("zhangsan");

stu1.setSex(new StringBuffer("男"));

s1.setStu(stu1);

System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode());

School s2 = s1.clone(); //調用重寫的clone方法,clone出一個新的school---s2

System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());

//修改s2中的值,看看是否會對s1中的值造成影響

s2.setSchoolName("希望小學");

s2.setStuNums(200);

Student stu2 = s2.getStu();

stu2.setAge(30);

stu2.setName("lisi");

stu2.setSex(stu2.getSex().append("6666666"));

s2.setStu(stu2);

//再次打印兩個school,查看結果

System.out.println("-------------------------------------------------------------------------");

System.out.println("s1: "+s1+" hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode());

System.out.println("s2: "+s2+" hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());

}

}

Cloneable接口的作用與深入理解深度克隆與淺度克隆

這裡可以看到兩個stu的hashcode已經不同了,說明這已經是兩個對象了,但是在s2中修改sex的值,為什麼還會影響到s1呢?

原因在於sex的類型是Stringbuffer,在clone的時候將StringBuffer對象的地址傳遞了過去,而StringBuffer類型沒有實現cloneable接口,也沒有重寫clone方法。

這種情況應該怎麼解決呢?

1.只實現淺度clone

2.stu2.setSex(new StringBuffer("newString")); 在設置stu2的sex時創建一個新的StringBuffer對象。

Cloneable接口的作用與深入理解深度克隆與淺度克隆

原文鏈接:https://blog.csdn.net/qq_37113604/article/details/81168224


分享到:


相關文章: