天天看點

java.lang.Object.clone()分析

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

要點總結

1.要使用object的clone方法的前提:

a)子類要實作Cloneable接口,否則調用clone()方法,會抛CloneNotSupportedException

b)子類要重寫clone()方法,然後修改包通路權限為public

c)在clone()方法中要調用super.clone()方法,是實作複制的核心

d)clone()方法,傳回的是Object類型,自己要注意類型轉換

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);來達到相同的效果

首先,看一下源碼:

public class Object  {
     protected native Object clone() throws CloneNotSupportedException;
 }
           

  由源代碼我們會發現:

  第一: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中對象的克隆

  • 在派生類中實作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",);
        Person p2=(Person)p1.clone();
        p2.setName("lis");
        p2.setAge();
        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",);
        Student s1=new Student("zhangsan",,p);
        Student s2=(Student)s1.clone();
        s2.getPro().setName("maer");
        s2.getPro().setAge();
        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",);
        Student s1=new Student("zhangsan",,p);
        Student s2=(Student)s1.clone();
        s2.getPro().setName("maer");
        s2.getPro().setAge();
        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;
    }
}
           

  繼續深究:

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

public class ArrayClone {
    public static void main(String[] args) {
        int[] a1={,,,};
        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));
        //[[email protected], [email protected]]
    }
}
           

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

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

複制代碼

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

  2、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",);
        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

轉載于:https://www.cnblogs.com/chenny3/p/10226132.html