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中對象的克隆(object 原生的clone() 必須配合 Cloneable 使用,否則無效(實現的是淺拷貝))
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 異常。
程序示例分析:
package demo.clone; /** * 如果 clone 類沒有實現 Cloneable 接口,並調用了 Object 的 clone() 方法(也就是調用了 super.Clone() 方法), * 那麼Object 的 clone() 方法就會拋出 CloneNotSupportedException 異常。 * 所以,要使用Object中的 clone()就要實現 Cloneable接口 * @author Administrator * */ public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public Object clone() throws CloneNotSupportedException { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { throw e; } return o; } 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; } }
package demo.clone; public class PersonTest { public static void main(String[] args) { try { Person p1 = new Person("zhangsan", 18); Person p2 = (Person) p1.clone(); //拋異常,以爲使用了object中的clone()卻沒有實現Cloneable接口 p2.setName("lis"); p2.setAge(20); System.out.println("name=" + p1.getName() + ",age=" + p1.getAge()); } catch (Exception e) { e.printStackTrace(); } } }
說明:
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 }
package demo.clone;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 使用序列化深度克隆雖然很簡單,
* 限制:但是需要所有需要序列的屬性對象必須實現Serializable接口
* @author Administrator
*
*/
public class Cat implements Serializable {
private static final long serialVersionUID = 1L;
private String name = null;
private Fish fish = null;
public Cat() {
}
public Cat(String name, Fish fish) {
this.name = name;
this.fish = fish;
}
@Override
public Object clone() throws CloneNotSupportedException {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
Object obj = null;
try {
// 將對象寫到流中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 從流中讀出來
ByteArrayInputStream bis = new ByteArrayInputStream(
bos.toByteArray());
ois = new ObjectInputStream(bis);
obj = ois.readObject();
} catch (Exception e) {
e.printStackTrace();
throw new CloneNotSupportedException(e.getMessage());
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return obj;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Fish getFish() {
return fish;
}
public void setFish(Fish fish) {
this.fish = fish;
}
public static void main(String[] args) {
Cat c1 = new Cat("tomcat", new Fish("old fish"));
try {
Cat c2 = (Cat) c1.clone();
System.out.println("fish: " + c1.getFish().getName());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Fish implements Serializable {
private static final long serialVersionUID = 1L;
private String name = null;
public Fish() {
}
public Fish(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
繼續深究:{一下是個人未能想明白的一些問題,網絡上也沒能給出很好的解釋}
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)