1. 程式人生 > >java中clone方法的理解(深拷貝、淺拷貝)

java中clone方法的理解(深拷貝、淺拷貝)

前言:

java中的clone一直是一個老生常談的問題,另外關於克隆網上也有很多的寫過這方面的問題。
我在這裡記錄一下我遇到的問題和使用clone的方法。

知識點一:什麼是淺拷貝?

    我們這裡說的淺拷貝是指我們拷貝出來的物件內部的引用型別變數和原來物件內部引用型別變數是同一引用(指向同一物件)。
    但是我們拷貝出來的物件和新物件不是同一物件。

    簡單來說,新(拷貝產生)、舊(元物件)物件不同,但是內部如果有引用型別的變數,新、舊物件引用的都是同一引用。

知識點二:什麼是深拷貝?

深拷貝:全部拷貝原物件的內容,包括記憶體的引用型別也進行拷貝

知識點三、java拷貝(clone)的前提:

1.首先我們需要知道Object類中一個clone()的方法,並且是protected關鍵字修飾的本地方法(使用native關鍵字修飾),我們完成克隆需要重寫該方法。
注意:按照慣例重寫的時候一個要將protected修飾符修改為public,這是JDK所推薦的做法,但是我測試了一下,
複寫的時候不修改為public也是能夠完成拷貝的。但是還是推薦寫成public2.我們重寫的clone方法一個要實現Cloneable介面。雖然這個介面並沒有什麼方法,但是必須實現該標誌介面。
如果不實現將會在執行期間丟擲:CloneNotSupportedException異常

3.Object中本地clone
()方法,預設是淺拷貝

知識點四:淺拷貝案例:

拷貝類:

package cn.cupcat.java8;

/**
 * Created by xy on 2017/12/25.
 */
public class Person implements Cloneable{
    private String name;
    private int age;
    private int[] ints;

    public int[] getInts() {
        return ints;
    }

    public Person(String name, int
age, int[] ints) { this.name = name; this.age = age; this.ints = ints; } public void setInts(int[] ints) { this.ints = ints; } 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 Person(String name, int age) { this.name = name; this.age = age; } /** * 預設實現 * */ @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }

測試類:

package cn.cupcat.java8;

import org.junit.Test;

/**
 * Created by xy on 2017/12/25.
 */
public class CloneTest  {

    @Test
    public void test() throws CloneNotSupportedException {
        int[] ints = {1,2,3};
        String name = "zhangxiangyang";
        int age = 23;
        Person person = new Person("zhangxiangyang",age,ints);
        System.out.print("一:克隆前:  age = "+ age + "... name = "+ name + " 陣列:");
        for (int i : ints){
            System.out.print(i + " ");
        }

        System.out.println();


        //拷貝
        Person clonePerson = (Person) person.clone();

        int clonePersonAge = clonePerson.getAge();
        String clonePersonName = clonePerson.getName();
        int[] ints1 = clonePerson.getInts();

        System.out.print("二:克隆後: age = "+ clonePersonAge + "... name = "+ clonePersonName + " 陣列: ");
        for (int i : ints1){
            System.out.print(i + " ");
        }
        System.out.println();
        //修改:
        ints1[0] = 50;
        //修飾
        clonePerson.setName("666666666");



        age = person.getAge();
        name = person.getName();
        System.out.println();
        System.out.print("三:修改後原物件: age = "+ age + "... name = "+ name + "陣列 ");
        for (int i : ints){
            System.out.print(i + " ");
        }
        System.out.println();
        System.out.println("四:person == clonePerson ? "+ (person == clonePerson ));
    }
}

結果為:

一:克隆前:  age = 23... name = zhangxiangyang 陣列:1 2 3 
二:克隆後: age = 23... name = zhangxiangyang 陣列: 1 2 3 

三:修改後原物件: age = 23... name = zhangxiangyang陣列 50 2 3 
四:person == clonePerson ? false

總結:

1.通過四輸出  person == clonePerson ? false 可以看出,克隆以後的物件已經和以前不是同一物件了。因為其引用是不同的。
2.通過分析一、二可以看出我們的拷貝成功執行了,並且拷貝的資料也都正確
3.通過分析三,我們修改了拷貝後物件的name、ints
        ints1[0] = 50;
        //修飾
        clonePerson.setName("666666666");
  發現原物件中的ints也跟著修改了,因此可以證明拷貝後的物件和原物件的ints陣列指向了同一引用。
  而我們發現同時也修改了拷貝物件name屬性,為什麼那麼原物件中的name屬性沒有發生改變呢?
  而且String型別也是引用型別呀? 別急,下面我會畫圖示意。
4. 通過以上總結,我們得出結論:clone()方法的預設實現是淺拷貝       

下面通過畫圖示意:

拷貝成功後:
這裡寫圖片描述

修改拷貝物件過後:
這裡寫圖片描述

ps:如果看不清楚圖片,可以在標籤視窗開啟

知識點五:深拷貝案例:

該類只和淺拷貝在clone方法的實現上有區別,其他地方相同:

package cn.cupcat.java8;

/**
 * Created by xy on 2017/12/25.
 */
public class Person implements Cloneable{
    private String name;
    private int age;
    private int[] ints;

    public int[] getInts() {
        return ints;
    }

    public Person(String name, int age, int[] ints) {
        this.name = name;
        this.age = age;
        this.ints = ints;
    }

    public void setInts(int[] ints) {
        this.ints = ints;
    }

    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 Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     *  深拷貝
     * */
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person person = new Person(name,age);
        int[] ints = new int[this.ints.length];
        System.arraycopy(this.ints,0,ints,0,ints.length);
        person.setInts(ints);

        return  person;
    }
}

該測試類和前拷貝測試類相同:

package cn.cupcat.java8;

import org.junit.Test;

/**
 * Created by xy on 2017/12/25.
 */
public class CloneTest  {

    @Test
    public void test() throws CloneNotSupportedException {
        int[] ints = {1,2,3};
        String name = "zhangxiangyang";
        int age = 23;
        Person person = new Person("zhangxiangyang",age,ints);
        System.out.print("克隆前:  age = "+ age + "... name = "+ name + " 陣列:");
        for (int i : ints){
            System.out.print(i + " ");
        }

        System.out.println();


        //拷貝
        Person clonePerson = (Person) person.clone();

        int clonePersonAge = clonePerson.getAge();
        String clonePersonName = clonePerson.getName();
        int[] ints1 = clonePerson.getInts();

        System.out.print("克隆後: age = "+ clonePersonAge + "... name = "+ clonePersonName + " 陣列: ");
        for (int i : ints1){
            System.out.print(i + " ");
        }
        System.out.println();
        //修改:
        ints1[0] = 50;
        //修飾可控後的物件
        clonePerson.setName("666666666");



        age = person.getAge();
        name = person.getName();
        System.out.println();
        System.out.print("修改後原物件: age = "+ age + "... name = "+ name + "陣列 ");
        for (int i : ints){
            System.out.print(i + " ");
        }
        System.out.println();
        System.out.println("person == clonePerson ? "+ (person == clonePerson ));
    }
}

結果為:

一:克隆前:  age = 23... name = zhangxiangyang 陣列:1 2 3 
二:克隆後: age = 23... name = zhangxiangyang 陣列: 1 2 3 

三:修改後原物件: age = 23... name = zhangxiangyang陣列 1 2 3 
四:person == clonePerson ? false

總結:

1.通過四可以看出完成了拷貝,並且克隆物件和原物件不是同一物件(沒有同一引用)
2.通過一、二可以看出完成了拷貝並且資料正確
3.通過三,我們修改克隆以後的物件,列印原物件發現沒有影響到原物件的資料,也就是說完成的深拷貝
  下面使用圖解說明一下

畫圖說明:

拷貝完成後:
這裡寫圖片描述

修改拷貝後物件後:

這裡寫圖片描述

知識點六:總結

拷貝也算是我們經常使用的一個方法,但是如果是不明白其中原理的程式設計師可能還是會入坑的。下面總結幾條使用建議:
 1.一定要實現Cloneable介面
 2.複寫clone()方法,注意:預設是淺拷貝,這裡需要將引用型別進行深拷貝處理
 3.特殊:String類雖然是引用型別,但是是final類,同時也有字串常量池的存在,不必進行處理