浅述深拷贝的多实现

125 阅读4分钟

我正在参加「掘金·启航计划」

上一篇大概的聊了下深拷贝和浅拷贝

了解到了两种拷贝方式的区别和简单的实现。

那么,这篇我们就来深入的聊一聊深拷贝有哪些实现方式。

多实现

方式一:实现Cloneable接口

首先他也可以通过实现Cloneable接口来实现深拷贝。

可以参考上篇文章:聊一聊深拷贝和浅拷贝,这里就不赘述了。

方式二:序列化

既然深拷贝是一种创建对象的方式,而序列化也能创建一个新对象,那么是不是通过序列化也可以实现深拷贝呢?

本文暂且介绍三种序列化方式。

  • SerializationUtils
  • Fastjson
  • Jackson

我们先来创建好可以公用的测试类

  • 创建一个被引用类Student
@Builder
@Data
class Student implements Serializable{
    private String name;

    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
  • 再创建一个拷贝类DeepCopyBySerialize
@Data
class DeepCopyBySerialize implements Serializable {
    private Student student;

    private int version;

    public DeepCopyBySerialize() {
    }

    public DeepCopyBySerialize(Student student, int version) {
        this.student = student;
        this.version = version;
    }
}

SerializationUtils

我们首先使用SerializationUtils工具类试试深拷贝

直接调用SerializationUtils的静态方法clone就可以创建出一个新的DeepCopyBySerialize对象了。

DeepCopyBySerialize deepCopyBySerialize1 = new DeepCopyBySerialize(Student.builder().name("学生小孙").age(12).build(), 3);
DeepCopyBySerialize deepCopyBySerialize2 = SerializationUtils.clone(deepCopyBySerialize1);
System.out.println("SerializationUtils深拷贝复制的对象和原对象是否是用一个对象?" + (deepCopyBySerialize1 == deepCopyBySerialize2));
System.out.println("SerializationUtils深拷贝复制对象和原对象的引用类型的属性是否指向同一个地址?" + (deepCopyBySerialize1.getStudent() == (deepCopyBySerialize2.getStudent())));
System.out.println("SerializationUtils深拷贝复制对象和原对象的基本类型的属性是否相等?" + (deepCopyBySerialize1.getVersion() == deepCopyBySerialize2.getVersion()));

System.out.println("===================org.apache.commons.lang3.SerializationUtils序列化深拷贝 is end===============");

Fastjson

还可以使用Fastjson工具包

  • 先将deepCopyBySerialize1序列化成字符串
  • 再将字符串反序列化成DeepCopyBySerialize对象
String deepCopyBySerialize1Str = JSONObject.toJSONString(deepCopyBySerialize1);
DeepCopyBySerialize deepCopyBySerialize3 = JSONObject.parseObject(deepCopyBySerialize1Str, DeepCopyBySerialize.class);
System.out.println("fastjson深拷贝复制的对象和原对象是否是用一个对象?" + (deepCopyBySerialize1 == deepCopyBySerialize3));
System.out.println("fastjson深拷贝复制对象和原对象的引用类型的属性是否指向同一个地址?" + (deepCopyBySerialize1.getStudent()==(deepCopyBySerialize3.getStudent())));
System.out.println("fastjson深拷贝复制对象和原对象的基本类型的属性是否相等?" + (deepCopyBySerialize1.getVersion() == deepCopyBySerialize3.getVersion()));

System.out.println("===================fastjson序列化深拷贝 is end===============");

Jackson

自然也少不了Jackson工具包

和Fastjson类似,先序列化然后反序列化生成一个新的对象。

ObjectMapper objectMapper = new ObjectMapper();
DeepCopyBySerialize deepCopyBySerialize4 = objectMapper.readValue(objectMapper.writeValueAsString(deepCopyBySerialize1), DeepCopyBySerialize.class);
System.out.println("jackson深拷贝复制的对象和原对象是否是用一个对象?" + (deepCopyBySerialize1 == deepCopyBySerialize4));
System.out.println("jackson深拷贝复制对象和原对象的引用类型的属性是否指向同一个地址?" + (deepCopyBySerialize1.getStudent()==(deepCopyBySerialize4.getStudent())));
System.out.println("jackson深拷贝复制对象和原对象的基本类型的属性是否相等?" + (deepCopyBySerialize1.getVersion() == deepCopyBySerialize4.getVersion()));

System.out.println("===================jackson序列化深拷贝 is end===============");

我们执行下,看看效果,是否能符合深拷贝的特性。

image.png

可以看到这三种序列化方式确实实现了深拷贝。

但是使用它们还有一些注意点

  • 要使用SerializationUtils进行深拷贝,需要拷贝类和引用对象都实现Serializable接口
  • 要使用Fastjson和Jackson进行深拷贝,需要拷贝类和引用类都有无参构造。

方式三:反射

既然能够使用序列化实现深拷贝,那么可不可以使用反射来实现呢?

我们来回顾一下深拷贝

  • 首先他会创建一个新对象
  • 并且新对象的引用类型属性跟源对象的属性引用的对象不是同一个对象

所以,用反射为什么不行!

那我们来思考一下,怎么用反射实现一个深拷贝呢?

  • 创建一个新对象不难,知道源对象的class对象就会有“一万种”创建新对象的方式
  • 关键是属性的引用对象也得是个新对象,可能引用的对象类的属性也是一个引用对象。。。
  • 所以我们得获取到对象的所有属性,判断其是否为引用类型。
  • 如果是引用类型,反射创建一个新对象set值到相关属性,并检查其所有属性是否为引用类型。
  • 依次递归。
class DeepCopyByReflectionUtil{

    public Object clone(Object obj,Object targetObj) throws InstantiationException, IllegalAccessException {
        if (obj instanceof String){
            return obj;
        }

        Class<?> objClass = obj.getClass();

        for (Field declaredField : objClass.getDeclaredFields()) {
            declaredField.setAccessible(true);
            // 获取字段的值
            Object value = declaredField.get(obj);
            Class<?> childClass = value.getClass();
            Object childTargetValue = null;
            // 引用类型 递归赋值
            if (!childClass.isPrimitive() && !(value instanceof Number || value instanceof String)){
                childTargetValue = childClass.newInstance();
                clone(value,childTargetValue);
            }
            // 基本类型
            else {
                childTargetValue = value;
            }

            declaredField.set(targetObj,childTargetValue);
        }
        return targetObj;
    }
 }

总结

深拷贝的方式有很多种。

主要分为

  • 实现Cloneable接口
  • 序列化
  • 反射

文中如有不足之处,欢迎指正!一起交流,一起学习,一起成长 ^v^