JAVA -- 对象深拷贝

继承 Cloneable 接口,并重新实现 clone 方法

需要实体类继承 Cloneable 接口,并重新实现 clone() 方法。
需要注意的是,实体类中引用的指针类型元素也要继承 Cloneable 接口,并重新实现 clone() 方法,或者是重新新建一个对象赋值给元素。

@Data
public class CloneableEntity implements Cloneable{
    
    
    private String stringValue;
    private PointerEntity pointerEntity;
    private List<String> listValue=  new ArrayList<>();

    //填充列表 用来比较克隆效率
    public void initListValue() {
    
    
        for (int i = 0; i < 10000; i++) {
    
    
            this.listValue.add(UUID.randomUUID().toString());
        }
    }

    @Override
    public CloneableEntity clone() throws CloneNotSupportedException {
    
    
        CloneableEntity clone = (CloneableEntity)super.clone();
		// 指针类型的元素要做clone一份新的
		if(null!=this.pointerEntity) {
    
    
        	clone.setPointerEntity(this.pointerEntity.clone());
        }
        // 创建一个新的对象给元素
        clone.setListValue(new ArrayList<>(listValue));
        return clone;
    }
}
@Data
public class PointerEntity implements Cloneable{
    
    
    private String uuid;

    public PointerEntity(){
    
    
    }

    public PointerEntity(String uuid){
    
    
        this.uuid = uuid;
    }

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

克隆实例

克隆出来的新对象中的元素做任何修改,都不会影响原来对象的中元素的值。

Gson gson = new Gson();//用来打印对象
//        Cloneable 深拷贝
CloneableEntity entity1 = new CloneableEntity();
entity1.setStringValue("1");
entity1.setPointerEntity(new PointerEntity(UUID.randomUUID().toString()));
entity1.setListValue(new ArrayList<String>(){
    
    {
    
    add("1");add("2");}});
CloneableEntity clone1 = entity1.clone();
//对克隆后的对象进行修改
clone1.setStringValue("1--1");
clone1.getListValue().add("3");
clone1.getPointerEntity().setUuid(UUID.randomUUID().toString());
System.out.println("entity1: "+gson.toJson(entity1));
System.out.println("clone1: "+gson.toJson(clone1));
-------------------------------
entity1: {
    
    "stringValue":"1","pointerEntity":{
    
    "uuid":"27bb47c6-1ad3-4b79-a408-509f2163b5fa"},"listValue":["1","2"]}
clone1: {
    
    "stringValue":"1--1","pointerEntity":{
    
    "uuid":"a3495d1d-e32c-4dee-be57-0a8a3f30678c"},"listValue":["1","2","3"]}

使用流进行序列化、反序列化

克隆的类必须支持序列化,类中的元素也必须支持序列化。否则在克隆是会爆出不支持序列化异常。

@Data
public class Serialzable2Entity implements Serializable {
    
    
    private String stringValue;
    private SerialzablePointerEntity pointerEntity;
    private List<String> listValue=  new ArrayList<>();

    //填充列表 用来比较克隆效率
    public void initListValue() {
    
    
        for (int i = 0; i < 10000; i++) {
    
    
            this.listValue.add(UUID.randomUUID().toString());
        }
    }
}
@Data
public class SerialzablePointerEntity implements Serializable {
    
    
    private String uuid;
    public SerialzablePointerEntity(){
    
    
    }

    public SerialzablePointerEntity(String uuid){
    
    
        this.uuid = uuid;
    }

}

克隆实例

Gson gson = new Gson();//用来打印对象
//流深拷贝
Serialzable2Entity entity4 = new Serialzable2Entity();
entity4.setStringValue("41");
entity4.setPointerEntity(new SerialzablePointerEntity(UUID.randomUUID().toString()));
entity4.setListValue(new ArrayList<String>(){
    
    {
    
    add("41");add("42");}});
Serialzable2Entity clone4 ;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
  ObjectOutputStream oos = new ObjectOutputStream(bos);
) {
    
    
 oos.writeObject(entity4);
 oos.flush();
 try (
      ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
      ObjectInputStream ois = new ObjectInputStream(bis);
 ) {
    
    
     clone4 = (Serialzable2Entity) ois.readObject();
 }
}
clone4.setStringValue("41--1");
clone4.getListValue().add("43");
clone4.getPointerEntity().setUuid(UUID.randomUUID().toString());
System.out.println("entity4: " + gson.toJson(entity4));
System.out.println("clone4: " + gson.toJson(clone4));
---------------------------------------
entity4: {
    
    "stringValue":"41","pointerEntity":{
    
    "uuid":"af3c287f-1289-4fb3-947f-f5b7684ef07e"},"listValue":["41","42"]}
clone4: {
    
    "stringValue":"41--1","pointerEntity":{
    
    "uuid":"0549e071-d7df-469f-baae-578e9aa89194"},"listValue":["41","42","43"]}

使用 Apache Commons Lang 序列化工具

Apache Commons Lang 自带了序列化工具类 SerializationUtils,其中的 clone() 方法允许我们对对象进行深拷贝。
其实它的原理和上面流的序列化、反序列化是一样的,只不过多了一些类型转化及其他操作在里面。因此它
同样需要克隆的类必须支持序列化,类中的元素也必须支持序列化。否则在克隆是会爆出不支持序列化异常。

克隆实例

Gson gson = new Gson();//用来打印对象
// Apache Commons Lang序列化深拷贝 拷贝对象和对象中所有涉及到的指针类型的对象都要实现序列化
Serialzable2Entity entity2 = new Serialzable2Entity();
entity2.setStringValue("11");
entity2.setPointerEntity(new SerialzablePointerEntity(UUID.randomUUID().toString()));
entity2.setListValue(new ArrayList<String>(){
    
    {
    
    add("11");add("12");}});
Serialzable2Entity clone2 = SerializationUtils.clone(entity2);
//对克隆后的对象进行修改
clone2.setStringValue("11--1");
clone2.getListValue().add("13--1");
clone2.getPointerEntity().setUuid(UUID.randomUUID().toString());
System.out.println("entity2: "+gson.toJson(entity2));
System.out.println("clone2: "+gson.toJson(clone2));
------------------------------------------
entity2: {
    
    "stringValue":"11","pointerEntity":{
    
    "uuid":"303e0bff-ad81-4b56-a1db-311cf894b189"},"listValue":["11","12"]}
clone2: {
    
    "stringValue":"11--1","pointerEntity":{
    
    "uuid":"bb42fd5b-d0ce-483a-9f34-d32e1b0eab5c"},"listValue":["11","12","13--1"]}

使用 Gson 序列化、反序列化

使用 Gson 序列化、反序列化去进行克隆,克隆对象不需要做任何扩展。

@Data
public class NormalEntity{
    
    
    private String stringValue;
    private NormalPointerEntity pointerEntity;
    private List<String> listValue=  new ArrayList<>();

    /**
     * 初始化列表
     */
    public void initListValue() {
    
    
        for (int i = 0; i < 10000; i++) {
    
    
            this.listValue.add(UUID.randomUUID().toString());
        }
    }
}

@Data
public class NormalPointerEntity{
    
    
    private String uuid;

    public NormalPointerEntity(){
    
    
    }

    public NormalPointerEntity(String uuid){
    
    
        this.uuid = uuid;
    }
}
Gson gson = new Gson();
// Gson 序列化反序列化深拷贝 对对象没有要求
NormalEntity entity3 = new NormalEntity();
entity3.setStringValue("21");
entity3.setPointerEntity(new NormalPointerEntity(UUID.randomUUID().toString()));
entity3.setListValue(new ArrayList<String>(){
    
    {
    
    add("21");add("22");}});
NormalEntity clone3 = gson.fromJson(gson.toJson(entity3 ), NormalEntity.class);
//修改克隆后的对象
clone3.setStringValue("21--1");
clone3.getListValue().add("23");
clone3.getPointerEntity().setUuid(UUID.randomUUID().toString());
System.out.println("entity3: "+gson.toJson(entity3));
System.out.println("clone3: "+gson.toJson(clone3));
---------------------------
entity3: {
    
    "stringValue":"21","pointerEntity":{
    
    "uuid":"b81d7ee9-54db-4d51-b09d-0a61adce4518"},"listValue":["21","22"]}
clone3: {
    
    "stringValue":"21--1","pointerEntity":{
    
    "uuid":"276de4f8-0b23-4435-9225-dfc836311064"},"listValue":["21","22","23"]}

优劣对比

首先效率上我们进行对比


StopWatch stopWatch = new StopWatch();
//        Cloneable 深拷贝
CloneableEntity entity1 = new CloneableEntity();
entity1.initListValue();
stopWatch.start("Cloneable 深拷贝");
CloneableEntity clone1 = entity1.clone();
stopWatch.stop();

//        流深拷贝
Serialzable2Entity entity4 = new Serialzable2Entity();
entity4.initListValue();
Serialzable2Entity clone4 ;
stopWatch.start(" 流深拷贝");
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
     ObjectOutputStream oos = new ObjectOutputStream(bos);
) {
    
    
    oos.writeObject(entity4);
    oos.flush();
    try (
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
    ) {
    
    
        clone4 = (Serialzable2Entity) ois.readObject();
    }
}
stopWatch.stop();

//        Apache Commons Lang序列化深拷贝 拷贝对象和对象中所有涉及到的指针类型的对象都要实现序列化
Serialzable2Entity entity2 = new Serialzable2Entity();
entity2.initListValue();
stopWatch.start("Apache Commons Lang序列化深拷贝");
Serialzable2Entity clone2 = SerializationUtils.clone(entity2);
stopWatch.stop();

//        Gson 序列化反序列化深拷贝 对对象没有要求
NormalEntity entity3 = new NormalEntity();
entity3.initListValue();
stopWatch.start(" Gson 序列化反序列化深拷贝");
Gson gson = new Gson();//用来打印对象
NormalEntity clone3 = gson.fromJson(gson.toJson(entity3 ), NormalEntity.class);
stopWatch.stop();

System.out.println(stopWatch.prettyPrint());
-----------------------------------
---------------------------------------------
ns         %     Task name
---------------------------------------------
000029100  000%  Cloneable 深拷贝
048922700  023%  流深拷贝
017047500  008%  Apache Commons Lang序列化深拷贝
150727000  070%  Gson 序列化反序列化深拷贝

根据耗时可以看出来,使用继承 Cloneable 接口克隆的效率最高,其次是使用流序列化和 SerializationUtils ,使用Gson序列化最慢。
其中要注意的是流深拷贝 和 SerializationUtils 的效率实际上应该是相差不大的。因为本质上都是使用的流去进行序列化和反序列化。但是由于在程序启动后第一次加载 IO 类时,会消耗一部分时间,所以在执行 SerializationUtils 克隆时,耗时比流序列化短。
当我们调换流序列化和 SerializationUtils 的执行顺序时,会发现流序列化消耗的时间比 SerializationUtils 短。

优劣总结

方法 优势 劣势
继承Cloneable接口 效率高,无需引用其他 jar 包,系统开销小 被克隆的类必须重写 Cloneable接口,选哟手动克隆类中的指针元素,当类中的字段更新时,我们还要及时更新clone方法,防止深拷贝失效
流序列化 无需引用其他 jar 包,添加、修改字段无需调整代码 效率一般,类必须支持序列化,序列化反序列化过程系统开销大
SerializationUtils.clone() 代码简洁,使用方便,添加、修改字段无需调整代码 效率一般,需要引入 apache common jar包 ,类必须支持序列化,序列化反序列化过程系统开销大
Gson序列化 不需要对类做任何预处理 效率很低,需要引入 jar 包,序列化反序列化过程系统开销大

猜你喜欢

转载自blog.csdn.net/qq_40096897/article/details/130700014