淺析克隆

建立對象的四大方法:一、new;二、反射;三、克隆;四、反序列化數組

今天來看一下如何克隆一個對象出來,克隆分爲2種,一種是淺克隆,一種是深克隆。ide

1、在淺克隆中,若是原型對象的屬性是值類型(如int,double,byte,boolean,char等),將複製一份給克隆對象;若是原型對象的屬性是引用類型(如類,接口,數組,集合等複雜數據類型),則將引用對象的地址複製一份給克隆對象,也就是說原型對象和克隆對象的屬性指向相同的內存地址。簡單來講,在淺克隆中,當原型對象被複制時只複製它自己和其中包含的值類型的屬性,而引用類型的屬性並無複製。工具

先定義一個附件類this

@Data
@AllArgsConstructor
public class Attachment {
    private String name;
    public void download() {
        System.out.println("下載附件,文件名爲" + name);
    }
}

定義一個週報類spa

@Data
public class WeeklyLog implements Cloneable {
    private Attachment attachment;

    @Override
    public WeeklyLog clone() {
        Object obj = null;
        try {
            obj = super.clone();
            return (WeeklyLog) obj;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

寫一個main方法來進行淺克隆對象

public class Client {
    public static void main(String[] args) {
        WeeklyLog log_previous = new WeeklyLog();
        WeeklyLog log_new = null;
        Attachment attachment = new Attachment("附件");
        log_previous.setAttachment(attachment);
        log_new = log_previous.clone();
        System.out.println("週報是否相同?" + (log_previous == log_new));
        System.out.println("附件是否相同?" + (log_previous.getAttachment() == log_new.getAttachment()));
    }
}

運行結果:接口

週報是否相同?false
附件是否相同?true內存

由此能夠看出log_prevlous跟log_new具備不一樣的內存地址,它們的附件對象內存地址相同。開發

2、深克隆,若是咱們要修改克隆出來的對象的引用屬性時就會把原型對象的該屬性也同時修改掉,咱們將main方法修改以下get

public class Client {
    public static void main(String[] args) {
        WeeklyLog log_previous = new WeeklyLog();
        WeeklyLog log_new = null;
        Attachment attachment = new Attachment("附件");
        log_previous.setAttachment(attachment);
        log_new = log_previous.clone();
        System.out.println("週報是否相同?" + (log_previous == log_new));
        System.out.println("附件是否相同?" + (log_previous.getAttachment() == log_new.getAttachment()));
        System.out.println(log_previous.getAttachment().getName());
        log_new.getAttachment().setName("新附件");
        System.out.println(log_previous.getAttachment().getName());
    }
}

運行結果:

週報是否相同?false
附件是否相同?true
附件
新附件

咱們能夠看到咱們修改了log_new,log_previous也跟着改了。

深克隆就是讓克隆對象的引用屬性跟原型對象沒有關係,由淺克隆的特性,咱們能夠知道,克隆出來的對象自己與原型對象是不一樣的內存地址的,由此咱們能夠將引用類型也添加克隆的特性,這樣就能夠將引用類型也分離出來。

附件對象修改以下

@Data
@AllArgsConstructor
public class Attachment implements Cloneable {
    private String name;
    public void download() {
        System.out.println("下載附件,文件名爲" + name);
    }

    @Override
    public Attachment clone() throws CloneNotSupportedException {
        return (Attachment)super.clone();
    }
}

週報對象修改以下

@Data
public class WeeklyLog implements Cloneable {
    private Attachment attachment;

    @Override
    public WeeklyLog clone() {
        WeeklyLog obj = null;
        try {
            obj = (WeeklyLog) super.clone();
            if (obj != null) {
                obj.setAttachment(obj.getAttachment().clone());
            }
            return obj;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

咱們再從新執行上述main方法

運行結果:

週報是否相同?false
附件是否相同?false
附件
附件

因而可知,深克隆後,附件對象的內存地址已經不同了,修改了克隆對象的附件地址,原型對象並不會受到影響。

可是若是原型對象中的引用屬性對象中又包含引用屬性,嵌套很是多,使用該方法來進行深度克隆就會很是麻煩了,這個時候咱們只可以使用序列化和反序列化來生成克隆對象了。此處咱們只使用Java的序列化和反序列化來作說明。

這裏全部的類都要加上序列化接口

附件類

@Data
@AllArgsConstructor
public class Attachment implements Cloneable,Serializable {
    private String name;
    public void download() {
        System.out.println("下載附件,文件名爲" + name);
    }

    @Override
    public Attachment clone() throws CloneNotSupportedException {
        return (Attachment)super.clone();
    }
}

週報類,添加深度克隆方法deepClone()

@Data
public class WeeklyLog implements Cloneable,Serializable {
    private Attachment attachment;

    @Override
    public WeeklyLog clone() {
        WeeklyLog obj = null;
        try {
            obj = (WeeklyLog) super.clone();
            if (obj != null) {
                obj.setAttachment(obj.getAttachment().clone());
            }
            return obj;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public WeeklyLog deepClone() throws IOException, ClassNotFoundException {
        //將對象寫入流中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);
        //將對象從流中取出
        ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream inputStream = new ObjectInputStream(arrayInputStream);
        return (WeeklyLog) inputStream.readObject();
    }
}

修改main()方法

public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        WeeklyLog log_previous = new WeeklyLog();
        WeeklyLog log_new = null;
        Attachment attachment = new Attachment("附件");
        log_previous.setAttachment(attachment);
        log_new = log_previous.deepClone();
        System.out.println("週報是否相同?" + (log_previous == log_new));
        System.out.println("附件是否相同?" + (log_previous.getAttachment() == log_new.getAttachment()));
        System.out.println(log_previous.getAttachment().getName());
        log_new.getAttachment().setName("新附件");
        System.out.println(log_previous.getAttachment().getName());
    }
}

運行結果:

週報是否相同?false
附件是否相同?false
附件
附件

如今咱們來看一下,它中間的二進制字節碼有多少,修改一下deepClone()方法,打印二進制字節碼

public WeeklyLog deepClone() throws IOException, ClassNotFoundException {
    //將對象寫入流中
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeObject(this);
    //將對象從流中取出
    System.out.println(Arrays.toString(byteArrayOutputStream.toByteArray()));
    ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    ObjectInputStream inputStream = new ObjectInputStream(arrayInputStream);
    return (WeeklyLog) inputStream.readObject();
}

運行上述main方法

[-84, -19, 0, 5, 115, 114, 0, 28, 99, 111, 109, 46, 103, 117, 97, 110, 106, 105, 97, 110, 46, 99, 108, 111, 110, 101, 46, 87, 101, 101, 107, 108, 121, 76, 111, 103, -10, -93, 81, -98, -8, -9, -59, 96, 2, 0, 2, 76, 0, 10, 97, 116, 116, 97, 99, 104, 109, 101, 110, 116, 116, 0, 31, 76, 99, 111, 109, 47, 103, 117, 97, 110, 106, 105, 97, 110, 47, 99, 108, 111, 110, 101, 47, 65, 116, 116, 97, 99, 104, 109, 101, 110, 116, 59, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 120, 112, 115, 114, 0, 29, 99, 111, 109, 46, 103, 117, 97, 110, 106, 105, 97, 110, 46, 99, 108, 111, 110, 101, 46, 65, 116, 116, 97, 99, 104, 109, 101, 110, 116, 113, -46, -107, -3, -36, 44, 1, -94, 2, 0, 1, 76, 0, 4, 110, 97, 109, 101, 113, 0, 126, 0, 2, 120, 112, 116, 0, 6, -23, -103, -124, -28, -69, -74, 116, 0, 6, -27, -111, -88, -26, -118, -91]
週報是否相同?false
附件是否相同?false
附件
附件

咱們能夠看到這個二進制的字節碼是很是長的,通常咱們在實際開發中是不使用Java自己的序列化方式的,如今咱們增長一種第三方的高效序列化工具kryo

在pom中增長依賴

<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo-shaded</artifactId>
    <version>4.0.2</version>
</dependency>

由於kryo在反序列化中須要無參構造器,因此咱們須要在WeeklyLog和Attachment中添加標籤@NoArgsConstructor

在WeeklyLog中添加方法deepCloneByKryo()

public WeeklyLog deepCloneByKryo() {
    Input input = null;
    try {
        Kryo kryo = new Kryo();
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        Output output = new Output(stream);
        kryo.writeObject(output, this);
        output.close();
        System.out.println(Arrays.toString(stream.toByteArray()));
        input = new Input(new ByteArrayInputStream(stream.toByteArray()));
        return kryo.readObject(input,WeeklyLog.class);
    }finally {
        input.close();
    }
}

修改main方法以下

public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        WeeklyLog log_previous = new WeeklyLog();
        log_previous.setName("週報");
        WeeklyLog log_new = null;
        Attachment attachment = new Attachment("附件");
        log_previous.setAttachment(attachment);
        log_new = log_previous.deepCloneByKryo();
        System.out.println("週報是否相同?" + (log_previous == log_new));
        System.out.println("附件是否相同?" + (log_previous.getAttachment() == log_new.getAttachment()));
        System.out.println(log_previous.getAttachment().getName());
        log_new.getAttachment().setName("新附件");
        System.out.println(log_previous.getAttachment().getName());
    }
}

運行結果:

[1, 1, 0, 99, 111, 109, 46, 103, 117, 97, 110, 106, 105, 97, 110, 46, 99, 108, 111, 110, 101, 46, 65, 116, 116, 97, 99, 104, 109, 101, 110, -12, 1, 1, -125, -23, -103, -124, -28, -69, -74, 1, -125, -27, -111, -88, -26, -118, -91] 週報是否相同?false 附件是否相同?false 附件 附件

相關文章
相關標籤/搜索