1. 程式人生 > >java物件的淺克隆和深克隆

java物件的淺克隆和深克隆

引言:

  在Object基類中,有一個方法叫clone,產生一個前期物件的克隆,克隆物件是原物件的拷貝,由於引用型別的存在,有深克隆和淺克隆之分,若克隆物件中存在引用型別的屬性,深克隆會將此屬性完全拷貝一份,而淺克隆僅僅是拷貝一份此屬性的引用。首先看一下容易犯的幾個小問題

  • clone方法是Object類的,並不是Cloneable介面的,Cloneable只是一個標記介面,標記介面是用使用者標記實現該介面的類具有某種該介面標記的功能,常見的標記介面有三個:Serializable、Cloneable、RandomAccess,沒有實現Cloneable介面,那麼呼叫clone方法就會爆出CloneNotSupportedException異常。
  • Object類中的clone方法是protected修飾的,這就表明我們在子類中不重寫此方法,就在子類外無法訪問,因為這個protected許可權是僅僅能在Object所在的包和子類能訪問的,這也驗證了子類重寫父類方法許可權修飾符可以變大但不能變小的說法。
    protected native Object clone() throws CloneNotSupportedException;
  • 重寫clone方法,內部僅僅是呼叫了父類的clone方法,其實是為了擴大訪問許可權,當然你可以把protected改為public,以後再繼承就不用重寫了。當然只是淺克隆的clone函式,深克隆就需要修改了。
        @Override
        
    protected Object clone() throws CloneNotSupportedException { return super.clone(); }
  • 屬性是String的情況,String也是一個類,那String引用型別嗎?String的表現有的像基本型別,歸根到底就是因為String不可改變,克隆之後倆個引用指向同一個String,但當修改其中的一個,改的不是String的值,卻是新生成一個字串,讓被修改的引用指向新的字串。外表看起來就像基本型別一樣。

淺克隆:

  淺克隆就是引用型別的屬性無法完全複製,類User中包含成績屬性Mark,Mark是由Chinese和math等等組成的,淺克隆失敗的例子

class Mark{
    private int chinese;
    private int math;
    public Mark(int chinese, int math) {
        this.chinese = chinese;
        this.math = math;
    }

    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    public void setMath(int math) {
        this.math = math;
    }

    @Override
    public String toString() {
        return "Mark{" +
                "chinese=" + chinese +
                ", math=" + math +
                '}';
    }
}
public class User implements Cloneable{
    private String name;
    private int age;
    private Mark mark;

    public User(String name, int age,Mark mark) {
        this.name = name;
        this.age = age;
        this.mark = mark;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", mark=" + mark +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Mark mark = new Mark(100,99);
        User user = new User("user",22,mark);
        User userClone = (User) user.clone();
        System.out.println("原user:"+user);
        System.out.println("克隆的user:"+userClone);
        //修改引用型別的mark屬性
        user.mark.setMath(60);
        System.out.println("修改後的原user:"+user);
        System.out.println("修改後的克隆user:"+userClone);
    }
}

  輸出結果為:   

    原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}    克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}    修改後的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}    修改後的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}

  很清楚的看到user的mark更改後,被克隆的user也修改了。而要想不被影響,就需要深克隆了。

深克隆:

 方式一:clone函式的巢狀呼叫

  既然引用型別無法被完全克隆,那將引用型別也實現Cloneable介面重寫clone方法,在User類中的clone方法呼叫屬性的克隆方法,也就是方法的巢狀呼叫

class Mark implements Cloneable{
    private int chinese;
    private int math;
    public Mark(int chinese, int math) {
        this.chinese = chinese;
        this.math = math;
    }
    public void setChinese(int chinese) {
        this.chinese = chinese;
    }
    public void setMath(int math) {
        this.math = math;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    @Override
    public String toString() {
        return "Mark{" +
                "chinese=" + chinese +
                ", math=" + math +
                '}';
    }
}
public class User implements Cloneable{
    private String name;
    private int age;
    private Mark mark;

    public User(String name, int age,Mark mark) {
        this.name = name;
        this.age = age;
        this.mark = mark;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", mark=" + mark +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        User user = (User) super.clone();
        user.mark = (Mark) this.mark.clone();
        return user;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Mark mark = new Mark(100,99);
        User user = new User("user",22,mark);
        User userClone = (User) user.clone();
        System.out.println("原user:"+user);
        System.out.println("克隆的user:"+userClone);
        //修改引用型別的mark屬性
        user.mark.setMath(60);
        System.out.println("修改後的原user:"+user);
        System.out.println("修改後的克隆user:"+userClone);
    }
}

  輸出結果為: 

    原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}    克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}    修改後的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}    修改後的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}

 方式二:序列化

  上一種方法已經足夠滿足我們的需要,但是如果類之間的關係很多,或者是有的屬性是陣列呢,陣列可無法實現Cloneable介面(我們可以在clone方法中手動複製陣列),但是每次都得手寫clone方法,很麻煩,而序列化方式只需要給每個類都實現一個Serializable介面,也是標記介面,最後同序列化和反序列化操作達到克隆的目的(包括陣列的複製)。序列化和反序列化的知識請參照下一篇

import java.io.*;
class Mark implements Serializable {
    private int chinese;
    private int math;
    public Mark(int chinese, int math) {
        this.chinese = chinese;
        this.math = math;
}
    public void setChinese(int chinese) {
        this.chinese = chinese;
    }
    public void setMath(int math) {
        this.math = math;
    }
    @Override
    public String toString() {
        return "Mark{" +
                "chinese=" + chinese +
                ", math=" + math +
                '}';
    }
}
public class User implements Serializable{
    private String name;
    private int age;
    private Mark mark;

    public User(String name, int age,Mark mark) {
        this.name = name;
        this.age = age;
        this.mark = mark;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", mark=" + mark +
                '}';
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Mark mark = new Mark(100,99);
        User user = new User("user",22,mark);

        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(user);//序列化
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        User userClone = (User) oi.readObject();//反序列化

        System.out.println("原user:"+user);
        System.out.println("克隆的user:"+userClone);
        user.mark.setMath(59);
        System.out.println("修改後的原user:"+user);
        System.out.println("修改後的克隆user:"+userClone);
    }
}

  輸出結果:    

    原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}    克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}    修改後的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}    修改後的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}

  帶陣列屬性的克隆:

import java.io.*;
import java.util.Arrays;

public class User implements Serializable{
    private String name;
    private int age;
    private int[] arr;

    public User(String name, int age, int[] arr) {
        this.name = name;
        this.age = age;
        this.arr = arr;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", arr=" + Arrays.toString(arr) +
                '}';
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        int[] arr = {1,2,3,4,5,6};
        User user = new User("user",22,arr);

        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(user);//序列化
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        User userClone = (User) oi.readObject();//反序列化

        System.out.println("原user:"+user);
        System.out.println("克隆的user:"+userClone);
        user.arr[1] = 9;
        System.out.println("修改後的原user:"+user);
        System.out.println("修改後的克隆user:"+userClone);
    }
}
View Code