天天看點

java object類中的clone_java.lang.Object.clone()解讀

java.lang.Object.clone()分析

首先,看一下源碼:

1 public classObject {2 protected native Object clone() throwsCloneNotSupportedException;3 }

由源代碼我們會發現:

第一:Object類的clone()方法是一個native方法,native方法的效率一般來說都是遠高于Java中的非native方法。這也解釋了為什麼要用Object中clone()方法而不是先new一個類,然後把原始對象中的資訊複制到新對象中,雖然這也實作了clone功能。(JNI是Java Native Interface的 縮寫。從Java 1.1開始,Java Native Interface (JNI)标準成為java平台的一部分,它允許Java代碼和其他語言寫的代碼進行互動。JNI一開始是為了本地已編譯語言,尤其是C和C++而設計的,但是它并不妨礙你使用其他語言,隻要調用約定受支援就可以了。使用java與本地已編譯的代碼互動,通常會喪失平台可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的,比如,使用一些舊的庫,與硬體、作業系統進行互動,或者為了提高程式的性能。JNI标準至少保證本地代碼能工作在任何Java 虛拟機實作下。)

第二:Object類中的 clone()方法被protected修飾符修飾。這也意味着如果要應用 clone()方 法,必須繼承Object類,在 Java中所有的類是預設繼承 Object類的,也就不用關心這點了。然後重載 clone()方法。還有一點要考慮的是為了讓其它類能調用這個 clone類的 clone()方法,重載之後要把 clone()方法的屬性設定為 public。

第三:Object.clone()方法傳回一個Object對象。我們必須進行強制類型轉換才能得到我們需要的類型。

淺層複制與深層複制概念:

淺層複制:被複制的對象的所有成員屬性都有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺層複制僅僅複制所考慮的對象,而不複制它所引用的對象。(概念不好了解,請結合下文的示例去了解)

深層複制:被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量将指向被複制過的新對象,而不是原有的那些被引用的對象。換言之,深層複制要複制的對象引用的對象都複制一遍。

Java中對象的克隆

1)在派生類中實作Cloneable借口。

2)為了擷取對象的一份拷貝,我們可以利用Object類的clone方法。

3)在派生類中覆寫積累的clone方法,聲明為public。

4)在派生類的clone方法中,調用super.clone()。

實作Cloneable接口

首先,看一下源碼:

1 public interfaceCloneable {2 }

我們奇怪的發現Cloneable竟然是空的,那麼我們為什麼要實作Cloneable接口呢?其實Cloneable接口僅僅是一個标志,而且這個标志也僅僅是針對 Object類中 clone()方法的,如果 clone 類沒有實作 Cloneable 接口,并調用了 Object 的 clone() 方法(也就是調用了 super.Clone() 方法),那麼Object 的 clone() 方法就會抛出 CloneNotSupportedException 異常。

程式示例分析:

1 public classPerson {2 privateString name;3 private intage;4 publicPerson(){}5 public Person(String name,intage){6 this.name=name;7 this.age=age;8 }9 publicObject clone(){10 Object o=null;11 try{12 o=super.clone();13 } catch(CloneNotSupportedException e) {14 e.printStackTrace();15 }16 returno;17 }18 publicString getName() {19 returnname;20 }21 public voidsetName(String name) {22 this.name =name;23 }24 public intgetAge() {25 returnage;26 }27 public void setAge(intage) {28 this.age =age;29 }30 }

1 public classPersonTest {2 public static voidmain(String[] args) {3 Person p1=new Person("zhangsan",18);4 Person p2=(Person)p1.clone();5 p2.setName("lis");6 p2.setAge(20);7 System.out.println("name="

8 +p1.getName()+",age="+p1.getAge());9 //修改p2後,沒有對p1産生影響。

10 }11 }

說明:

1)為什麼我們在派生類中覆寫Object的clone()方法時,一定要調用super.clone()呢?在運作時刻,Object中的clone()識别你要複制的是哪一個對象,然後為此對象配置設定空間,并進行對象的複制,将原始對象的内容一一複制到新對象的存儲空間中。

2)繼承自java.lang.Object.clone()方法是淺層複制。一下代碼可以證明之:

1 public class Student implementsCloneable {2 privateString name;3 private intage;4 privateProfessor pro;5 publicStudent(){}6 public Student(String name,intage,Professor pro){7 this.name=name;8 this.age=age;9 this.pro=pro;10 }11 publicObject clone(){12 Object o=null;13 try{14 //Object中的clone()識别出你要複制的是哪一個對象。

15 o=super.clone();16 } catch(CloneNotSupportedException e) {17 System.out.println(e.toString());18 }19 returno;20 }21 publicString getName() {22 returnname;23 }24 public voidsetName(String name) {25 this.name =name;26 }27 public intgetAge() {28 returnage;29 }30 public void setAge(intage) {31 this.age =age;32 }33 publicProfessor getPro() {34 returnpro;35 }36 public voidsetPro(Professor pro) {37 this.pro =pro;38 }39 }40 classProfessor{41 privateString name;42 private intage;43 publicProfessor(){}44 public Professor(String name,intage){45 this.name=name;46 this.age=age;47 }48 publicString getName() {49 returnname;50 }51 public voidsetName(String name) {52 this.name =name;53 }54 public intgetAge() {55 returnage;56 }57 public void setAge(intage) {58 this.age =age;59 }60 }

1 public classStudentTest {2 public static voidmain(String[] args) {3 Professor p=new Professor("wangwu",50);4 Student s1=new Student("zhangsan",18,p);5 Student s2=(Student)s1.clone();6 s2.getPro().setName("maer");7 s2.getPro().setAge(40);8 System.out.println("name="+s1.getPro().getName()9 +",age="+s1.getPro().getAge());10 //name=maer,age=40

11 }12 }

那麼我們如何實作深層複制的克隆,即在修改s2.Professor時不影響s1.Professor?代碼改進如下:

1 public class Student implementsCloneable {2 privateString name;3 private intage;4 Professor pro;5 publicStudent(){}6 public Student(String name,intage,Professor pro){7 this.name=name;8 this.age=age;9 this.pro=pro;10 }11 publicObject clone(){12 Student o=null;13 try{14 //Object中的clone()識别出你要複制的是哪一個對象。

15 o=(Student)super.clone();16 } catch(CloneNotSupportedException e) {17 System.out.println(e.toString());18 }19 o.pro=(Professor)pro.clone();20 returno;21 }22 publicString getName() {23 returnname;24 }25 public voidsetName(String name) {26 this.name =name;27 }28 public intgetAge() {29 returnage;30 }31 public void setAge(intage) {32 this.age =age;33 }34 publicProfessor getPro() {35 returnpro;36 }37 public voidsetPro(Professor pro) {38 this.pro =pro;39 }40 }41 class Professor implementsCloneable{42 privateString name;43 private intage;44 publicProfessor(){}45 public Professor(String name,intage){46 this.name=name;47 this.age=age;48 }49 publicObject clone(){50 Object o=null;51 try{52 o=super.clone();53 } catch(CloneNotSupportedException e) {54 e.printStackTrace();55 }56 returno;57 }58 publicString getName() {59 returnname;60 }61 public voidsetName(String name) {62 this.name =name;63 }64 public intgetAge() {65 returnage;66 }67 public void setAge(intage) {68 this.age =age;69 }70 }

public classStudentTest {public static voidmain(String[] args) {

Professor p=new Professor("wangwu",50);

Student s1=new Student("zhangsan",18,p);

Student s2=(Student)s1.clone();

s2.getPro().setName("maer");

s2.getPro().setAge(40);

System.out.println("name="+s1.getPro().getName()+",age="+s1.getPro().getAge());//name=wangwu,age=50

}

}

利用串行化來實作深層複制

把對象寫到流中的過程是串行化(Serilization)過程,而把對象從流中讀出來是并行化(Deserialization)過程。應當指出的是,寫在流中的是對象的一個拷貝,而原來對象仍然存在JVM裡面。

在Java語言裡深層複制一個對象,常常可以先使對象實作Serializable接口,然後把對象(實際上隻是對象的一個拷貝)寫到一個流中,再從流中讀出來,便可以重建對象。

這樣做的前提是對象以及對象内部所有引用到的對象都是可串行化的,否則,就需要仔細考察那些不可串行化的對象是否設成transient,進而将之排除在複制過程之外。代碼改進如下:

1 public class Student implementsSerializable {2 privateString name;3 private intage;4 Professor pro;5 publicStudent(){}6 public Student(String name,intage,Professor pro){7 this.name=name;8 this.age=age;9 this.pro=pro;10 }11 public Object deepClone() throwsIOException, ClassNotFoundException{12 //将對象寫到流中

13 ByteArrayOutputStream bo=newByteArrayOutputStream();14 ObjectOutputStream oo=newObjectOutputStream(bo);15 oo.writeObject(this);16 //從流中讀出來

17 ByteArrayInputStream bi=newByteArrayInputStream(bo.toByteArray());18 ObjectInputStream oi=newObjectInputStream(bi);19 returnoi.readObject();20 }21 publicString getName() {22 returnname;23 }24 public voidsetName(String name) {25 this.name =name;26 }27 public intgetAge() {28 returnage;29 }30 public void setAge(intage) {31 this.age =age;32 }33 publicProfessor getPro() {34 returnpro;35 }36 public voidsetPro(Professor pro) {37 this.pro =pro;38 }39 }40 class Professor implementsSerializable{41 privateString name;42 private intage;43 publicProfessor(){}44 public Professor(String name,intage){45 this.name=name;46 this.age=age;47 }48 publicString getName() {49 returnname;50 }51 public voidsetName(String name) {52 this.name =name;53 }54 public intgetAge() {55 returnage;56 }57 public void setAge(intage) {58 this.age =age;59 }60 }

1 public classStudentTest {2 public static void main(String[] args) throwsIOException, ClassNotFoundException {3 Professor p=new Professor("wangwu",50);4 Student s1=new Student("zhangsan",18,p);5 Student s2=(Student)s1.deepClone();6 s2.getPro().setName("maer");7 s2.getPro().setAge(40);8 System.out.println("name="+s1.getPro().getName()9 +",age="+s1.getPro().getAge());10 //name=wangwu,age=50

11 }12 }

繼續深究:{一下是個人未能想明白的一些問題,網絡上也沒能給出很好的解釋}

1、數組:(以int[]為例):

1 public classArrayClone {2 public static voidmain(String[] args) {3 int[] a1={1,2,3,4};4 int[] a2=a1.clone();5 System.out.println(Arrays.toString(a2));6 //[1, 2, 3, 4]

7 String[] s1={"hello","china"};8 String[] s2=s1.clone();9 System.out.println(Arrays.toString(s2));10 //[hello, china]

11 Object[] o1={new Object(),newObject()};12 Object[] o2=o1.clone();13 System.out.println(Arrays.toString(o2));14 //[[email protected], [email protected]]

15 }16 }

我們發現Java數組有clone()方法,而且不需要我們去進行強制類型轉換,Java底層是怎樣實作資料結構這個功能的?

1 public classArrayClone {2 public static voidmain(String[] args) {3 Person p1=new Person("wangwu",18);4 Person p2=new Person("lisi",28);5 Person[] ps1={p1,p2};6 Person[] ps2=ps1.clone();7 ps2[0].setName("wanghao");8 ps2[0].setAge(22);9 System.out.println("name="+p1.getName()+",age="+p1.getAge());10 //name=wanghao,age=22

11 }12 }

由測試可知,Java數組隻具備淺層複制的功能。

2、String類

1 public classStringClone {2 public static voidmain(String[] args) {3 String str1="wang";4 //String str2=(String)str1.clone();5 //編譯錯誤,String類沒有clone方法

6 }7 }

檢視源代碼,我們可知,String類并沒有重載Object類的clone方法。雖然,String和Object都在java.lang包中,但是我們的測試類StringClone不在java.lang包中,是以,str.clone()時會出現編譯錯誤。繼續進行:

1 public classDog {2 privateString name;3 private intage;4 publicDog(){}5 public Dog(String name,intage){6 this.name=name;7 this.age=age;8 }9 public static voidmain(String[] args) {10 Dog dog1=new Dog("dog1",5);11 Dog dog2=null;12 try{13 dog2=(Dog)dog1.clone();14 } catch(CloneNotSupportedException e) {15 //TODO Auto-generated catch block

16 e.printStackTrace();17 }18 System.out.println(dog2.getName()+","+dog2.getAge());19 }20 publicString getName() {21 returnname;22 }23 public voidsetName(String name) {24 this.name =name;25 }26 public intgetAge() {27 returnage;28 }29 public void setAge(intage) {30 this.age =age;31 }32 }

我們驚奇的發現,dog1.clone();并沒有出現變異錯誤,我們随便建立的類具有clone方法,這又是怎麼回事?

雖然沒編譯錯誤,但是運作時出錯,如下所示:

1 java.lang.CloneNotSupportedException: com.clone.Dog2 at java.lang.Object.clone(Native Method)3 at com.clone.Dog.main(Dog.java:15)4 Exception in thread "main"java.lang.NullPointerException5 at com.clone.Dog.main(Dog.java:20)