java.lang.Object.clone()分析

文章來源:http://www.cnblogs.com/gw811/archive/2012/10/07/2712252.htmlhtml


要點總結
1.要使用object的clone方法的前提:
a)子類要實現Cloneable接口,不然調用clone()方法,會拋CloneNotSupportedException
b)子類要重寫clone()方法,而後修改包訪問權限爲public
c)在clone()方法中要調用super.clone()方法,是實現複製的核心
d)clone()方法,返回的是Object類型,本身要注意類型轉換java

2.清楚什麼是淺層複製?什麼是深層複製
具體看文章分析,有講
3.執行clone(),默認是淺層複製仍是深層複製?
答:默認是淺層複製
4.執行clone()方法,要實現深層複製,該怎麼作?在代碼層面上深層複製比淺層複製多增長了什麼?
答:1)在成員屬性字段中,全部引用的對象都要實現Cloneable接口,並重寫clone()方法。在要實現深層複製的類的clone()方法中,要手動調用引用對象的clone(),而後將返回的副本手動注入到要主對象的對應成員字段中。2)對引用對象還要進行clone
5.經過序列化來實現深層複製,該怎麼作?
答:實現序列化Serializable接口,將對象寫入流中,再從流中取出,注意取出的對象是原對象的拷貝。並且仍是深層拷貝
6.java中對數組進行clone(),默認是淺層複製
7.String對象不能調用clone()方法
答:由於String對象沒有實現Cloneable接口,也沒有重寫clone()方法。因此不能調用。這也與String對象不可變的特性相對應。若是要對String對象進行淺拷貝能夠經過String a=」abc」;String b=a;這也來達到效果。若是要對String對象進行深拷貝能夠經過String a=」abc」;String b=new String(a);來達到相同的效果web

首先,看一下源碼:數組

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 虛擬機實現下。)svg

  第二:Object類中的 clone()方法被protected修飾符修飾。這也意味着若是要應用 clone()方 法,必須繼承Object類,在 Java中全部的類是缺省繼承 Object類的,也就不用關心這點了。而後重載 clone()方法。還有一點要考慮的是爲了讓其它類能調用這個 clone類的 clone()方法,重載以後要把 clone()方法的屬性設置爲 public。性能

  第三:Object.clone()方法返回一個Object對象。咱們必須進行強制類型轉換才能獲得咱們須要的類型。測試

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

淺層複製
被複制的對象的全部成員屬性都有與原來的對象相同的值,而全部的對其餘對象的引用仍然指向原來的對象。換言之,淺層複製僅僅複製的是成員的值,而不復制它所引用的對象,對於引用的對象只是複製的指向堆中的地址。(概念很差理解,請結合下文的示例去理解)this

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

  Java中對象的克隆

  • 在派生類中實現Cloneable藉口。
  • 爲了獲取對象的一份拷貝,咱們能夠利用Object類的clone方法。
  • 在派生類中覆蓋積累的clone方法,聲明爲public。
  • 在派生類的clone方法中,調用super.clone()。


  實現Cloneable接口

  首先,看一下源碼:  

public interface Cloneable { 
 }

  咱們奇怪的發現Cloneable居然是空的,那麼咱們爲何要實現Cloneable接口呢?其實Cloneable接口僅僅是一個標誌,並且這個標誌也僅僅是針對 Object類中 clone()方法的,若是 clone 類沒有實現 Cloneable 接口,並調用了 Object 的 clone() 方法(也就是調用了 super.Clone() 方法),那麼Object 的 clone() 方法就會拋出 CloneNotSupportedException 異常。

  程序示例分析

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(){
        Object o=null;
        try {
            o=super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        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;
    }
}
public class PersonTest {
    public static void main(String[] args) {
        Person p1=new Person("zhangsan",18);
        Person p2=(Person)p1.clone();
        p2.setName("lis");
        p2.setAge(20);
        System.out.println("name="
            +p1.getName()+",age="+p1.getAge());
        //修改p2後,沒有對p1產生影響。
    }
}

  說明:

  1)爲何咱們在派生類中覆蓋Object的clone()方法時,必定要調用super.clone()呢?在運行時刻,Object中的clone()識別你要複製的是哪個對象,而後爲此對象分配空間,並進行對象的複製,將原始對象的內容一一複製到新對象的存儲空間中。

  2)繼承自java.lang.Object.clone()方法是淺層複製。如下代碼能夠證實之:

public class Student implements Cloneable {
    private String name;
    private int age;
    private Professor pro;
    public Student(){}
    public Student(String name,int age,Professor pro){
        this.name=name;
        this.age=age;
        this.pro=pro;
    }
    public Object clone(){
        Object o=null;
        try {
            //Object中的clone()識別出你要複製的是哪個對象。
            o=super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        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;
    }
    public Professor getPro() {
        return pro;
    }
    public void setPro(Professor pro) {
        this.pro = pro;
    }
}
class Professor{
    private String name;
    private int age;
    public Professor(){}
    public Professor(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 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=maer,age=40
    }
}

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

public class Student implements Cloneable {
    private String name;
    private int age;
    private Professor pro;
    public Student(){}
    public Student(String name,int age,Professor pro){
        this.name=name;
        this.age=age;
        this.pro=pro;
    }
    public Object clone(){
        Object o=null;
        try {
            //Object中的clone()識別出你要複製的是哪個對象。
            o=super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        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;
    }
    public Professor getPro() {
        return pro;
    }
    public void setPro(Professor pro) {
        this.pro = pro;
    }
}
class Professor{
    private String name;
    private int age;
    public Professor(){}
    public Professor(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 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,從而將之排除在複製過程以外。代碼改進以下:

public class Student implements Serializable {
    private String name;
    private int age;
    Professor pro;
    public Student(){}
    public Student(String name,int age,Professor pro){
        this.name=name;
        this.age=age;
        this.pro=pro;
    }
    public Object deepClone() throws IOException, ClassNotFoundException{
        //將對象寫到流中
        ByteArrayOutputStream bo=new ByteArrayOutputStream();
        ObjectOutputStream oo=new ObjectOutputStream(bo);
        oo.writeObject(this);
        //從流中讀出來
        ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi=new ObjectInputStream(bi);
        return oi.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 Professor getPro() {
        return pro;
    }
    public void setPro(Professor pro) {
        this.pro = pro;
    }
}
class Professor implements Serializable{
    private String name;
    private int age;
    public Professor(){}
    public Professor(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;
    }
}

  繼續深究:
  一、數組:(以int[]爲例):

public class ArrayClone {
    public static void main(String[] args) {
        int[] a1={1,2,3,4};
        int[] a2=a1.clone();
        System.out.println(Arrays.toString(a2));
        //[1, 2, 3, 4]
        String[] s1={"hello","china"};
        String[] s2=s1.clone();
        System.out.println(Arrays.toString(s2));
        //[hello, china]
        Object[] o1={new Object(),new Object()};
        Object[] o2=o1.clone();
        System.out.println(Arrays.toString(o2));
        //[java.lang.Object@1fc4bec, java.lang.Object@dc8569]
    }
}

  咱們發現Java數組有clone()方法,並且不須要咱們去進行強制類型轉換,Java底層是怎樣實現數據結構這個功能的?

public class ArrayClone {
    public static void main(String[] args) {
        Person p1=new Person("wangwu",18);
        Person p2=new Person("lisi",28);
        Person[] ps1={p1,p2};
        Person[] ps2=ps1.clone();
        ps2[0].setName("wanghao");
        ps2[0].setAge(22);
        System.out.println("name="+p1.getName()+",age="+p1.getAge());
        //name=wanghao,age=22
    }
}

複製代碼
  由測試可知,Java數組只具有淺層複製的功能。

  二、String類

public class StringClone {
    public static void main(String[] args) {
        String str1="wang";
        //String str2=(String)str1.clone();
        //編譯錯誤,String類沒有clone方法
    }
}

複製代碼
  查看源代碼,咱們可知,String類並無重載Object類的clone方法。雖然,String和Object都在java.lang包中,可是咱們的測試類StringClone不在java.lang包中,所以,str.clone()時會出現編譯錯誤。繼續進行:

public class Dog {
    private String name;
    private int age;
    public Dog(){}
    public Dog(String name,int age){
        this.name=name;
        this.age=age;
    }
    public static void main(String[] args) {
        Dog dog1=new Dog("dog1",5);
        Dog dog2=null;
        try {
            dog2=(Dog)dog1.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(dog2.getName()+","+dog2.getAge());
    }
    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;
    }
}

  咱們驚奇的發現,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)

文章來源:http://www.cnblogs.com/gw811/archive/2012/10/07/2712252.html

相關文章
相關標籤/搜索