浅析克隆

建立对象的四大方法:一、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 附件 附件

相关文章
相关标签/搜索