java.lang.Object.clone()分析
首先,看一下源碼:
1 public class Object { 2 protected native Object clone() throws CloneNotSupportedException; 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 interface Cloneable { 2 }
我們奇怪的發現Cloneable竟然是空的,那么我們為什么要實現Cloneable接口呢?其實Cloneable接口僅僅是一個標志,而且這個標志也僅僅是針對 Object類中 clone()方法的,如果 clone 類沒有實現 Cloneable 接口,並調用了 Object 的 clone() 方法(也就是調用了 super.Clone() 方法),那么Object 的 clone() 方法就會拋出 CloneNotSupportedException 異常。
程序示例分析:
1 public class Person { 2 private String name; 3 private int age; 4 public Person(){} 5 public Person(String name,int age){ 6 this.name=name; 7 this.age=age; 8 } 9 public Object clone(){ 10 Object o=null; 11 try { 12 o=super.clone(); 13 } catch (CloneNotSupportedException e) { 14 e.printStackTrace(); 15 } 16 return o; 17 } 18 public String getName() { 19 return name; 20 } 21 public void setName(String name) { 22 this.name = name; 23 } 24 public int getAge() { 25 return age; 26 } 27 public void setAge(int age) { 28 this.age = age; 29 } 30 }
1 public class PersonTest { 2 public static void main(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 implements Cloneable { 2 private String name; 3 private int age; 4 private Professor pro; 5 public Student(){} 6 public Student(String name,int age,Professor pro){ 7 this.name=name; 8 this.age=age; 9 this.pro=pro; 10 } 11 public Object 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 return o; 20 } 21 public String getName() { 22 return name; 23 } 24 public void setName(String name) { 25 this.name = name; 26 } 27 public int getAge() { 28 return age; 29 } 30 public void setAge(int age) { 31 this.age = age; 32 } 33 public Professor getPro() { 34 return pro; 35 } 36 public void setPro(Professor pro) { 37 this.pro = pro; 38 } 39 } 40 class Professor{ 41 private String name; 42 private int age; 43 public Professor(){} 44 public Professor(String name,int age){ 45 this.name=name; 46 this.age=age; 47 } 48 public String getName() { 49 return name; 50 } 51 public void setName(String name) { 52 this.name = name; 53 } 54 public int getAge() { 55 return age; 56 } 57 public void setAge(int age) { 58 this.age = age; 59 } 60 }
1 public class StudentTest { 2 public static void main(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 implements Cloneable { 2 private String name; 3 private int age; 4 Professor pro; 5 public Student(){} 6 public Student(String name,int age,Professor pro){ 7 this.name=name; 8 this.age=age; 9 this.pro=pro; 10 } 11 public Object 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 return o; 21 } 22 public String getName() { 23 return name; 24 } 25 public void setName(String name) { 26 this.name = name; 27 } 28 public int getAge() { 29 return age; 30 } 31 public void setAge(int age) { 32 this.age = age; 33 } 34 public Professor getPro() { 35 return pro; 36 } 37 public void setPro(Professor pro) { 38 this.pro = pro; 39 } 40 } 41 class Professor implements Cloneable{ 42 private String name; 43 private int age; 44 public Professor(){} 45 public Professor(String name,int age){ 46 this.name=name; 47 this.age=age; 48 } 49 public Object clone(){ 50 Object o=null; 51 try { 52 o=super.clone(); 53 } catch (CloneNotSupportedException e) { 54 e.printStackTrace(); 55 } 56 return o; 57 } 58 public String getName() { 59 return name; 60 } 61 public void setName(String name) { 62 this.name = name; 63 } 64 public int getAge() { 65 return age; 66 } 67 public void setAge(int age) { 68 this.age = age; 69 } 70 }
public class StudentTest { public static void main(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 implements Serializable { 2 private String name; 3 private int age; 4 Professor pro; 5 public Student(){} 6 public Student(String name,int age,Professor pro){ 7 this.name=name; 8 this.age=age; 9 this.pro=pro; 10 } 11 public Object deepClone() throws IOException, ClassNotFoundException{ 12 //將對象寫到流中 13 ByteArrayOutputStream bo=new ByteArrayOutputStream(); 14 ObjectOutputStream oo=new ObjectOutputStream(bo); 15 oo.writeObject(this); 16 //從流中讀出來 17 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); 18 ObjectInputStream oi=new ObjectInputStream(bi); 19 return oi.readObject(); 20 } 21 public String getName() { 22 return name; 23 } 24 public void setName(String name) { 25 this.name = name; 26 } 27 public int getAge() { 28 return age; 29 } 30 public void setAge(int age) { 31 this.age = age; 32 } 33 public Professor getPro() { 34 return pro; 35 } 36 public void setPro(Professor pro) { 37 this.pro = pro; 38 } 39 } 40 class Professor implements Serializable{ 41 private String name; 42 private int age; 43 public Professor(){} 44 public Professor(String name,int age){ 45 this.name=name; 46 this.age=age; 47 } 48 public String getName() { 49 return name; 50 } 51 public void setName(String name) { 52 this.name = name; 53 } 54 public int getAge() { 55 return age; 56 } 57 public void setAge(int age) { 58 this.age = age; 59 } 60 }
1 public class StudentTest { 2 public static void main(String[] args) throws IOException, 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 class ArrayClone { 2 public static void main(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(),new Object()}; 12 Object[] o2=o1.clone(); 13 System.out.println(Arrays.toString(o2)); 14 //[java.lang.Object@1fc4bec, java.lang.Object@dc8569] 15 } 16 }
我們發現Java數組有clone()方法,而且不需要我們去進行強制類型轉換,Java底層是怎樣實現數據結構這個功能的?
1 public class ArrayClone { 2 public static void main(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 class StringClone { 2 public static void main(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 class Dog { 2 private String name; 3 private int age; 4 public Dog(){} 5 public Dog(String name,int age){ 6 this.name=name; 7 this.age=age; 8 } 9 public static void main(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 public String getName() { 21 return name; 22 } 23 public void setName(String name) { 24 this.name = name; 25 } 26 public int getAge() { 27 return age; 28 } 29 public void setAge(int age) { 30 this.age = age; 31 } 32 }
我們驚奇的發現,dog1.clone();並沒有出現變異錯誤,我們隨便創建的類具有clone方法,這又是怎么回事?
雖然沒編譯錯誤,但是運行時出錯,如下所示:
1 java.lang.CloneNotSupportedException: com.clone.Dog 2 at java.lang.Object.clone(Native Method) 3 at com.clone.Dog.main(Dog.java:15) 4 Exception in thread "main" java.lang.NullPointerException 5 at com.clone.Dog.main(Dog.java:20)