Java 拷贝各种实现方式对比(浅拷贝、深拷贝)

所有的测试代码在最下方。

1. 实现Cloneable接口

实现cloneable接口,重写clone类。

直接使用object的clone方法,还是浅拷贝,需要自己重写clone方法,注意拷贝的对象中还包含其他对象的话,包含的对象也需要重写clone方法。

这种方法效率最高,但是不好维护

/**
 * @Description:
 * @Author: zhaoyuan
 * @Date: 2021/4/14 0014
 * @Time 14:45
 */
public class Parent implements Cloneable{
    Integer id;

    String name;

    Child child;
    
    List<List<Child>> childrenList;

    List<Child> children;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Child getChild() {
        return child;
    }

    public void setChild(Child child) {
        this.child = child;
    }

    public List<List<Child>> getChildrenList() {
        return childrenList;
    }

    public void setChildrenList(List<List<Child>> childrenList) {
        this.childrenList = childrenList;
    }

    public List<Child> getChildren() {
        return children;
    }

    public void setChildren(List<Child> children) {
        this.children = children;
    }

    @Override
    protected Parent clone() throws CloneNotSupportedException {
        Parent cloneParent = (Parent)super.clone();
        if (Objects.nonNull(this.child)) {
            cloneParent.setChild(this.child.clone());
        }
        if (!CollectionUtils.isEmpty(this.children)) {
            List<Child> cloneChildren = new ArrayList<>();
            for (Child child : this.children) {
                cloneChildren.add(child.clone());
            }
            cloneParent.setChildren(cloneChildren);
        }
        if (!CollectionUtils.isEmpty(this.childrenList)) {
            List<List<Child>> cloneChildrenList = new ArrayList<>();
            for (List<Child> childList : this.childrenList) {
                List<Child> cloneChild = new ArrayList<>();
                for (Child child : childList) {
                    cloneChild.add(child.clone());
                }
                cloneChildrenList.add(cloneChild);
            }
            cloneParent.setChildrenList(cloneChildrenList);
        }
        
        return cloneParent;
    }
}

/**
 * @Description:
 * @Author: zhaoyuan
 * @Date: 2021/4/14 0014
 * @Time 14:48
 */
public class Child implements Cloneable{
    Integer id;

    String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

image-20210414154352632

2. BeanUtils 是浅clone

image-20210414154442550

3. 使用Orika是深clone,但是多层嵌套数组,clone有问题,谨慎使用

image-20210414154608123

4. 序列化Clone,深clone,但是效率不高

image-20210414154646360

5. 转json完成clone,深clone

我这里使用的hutool的json工具类转的,效率还没有序列化的方式高,但是其他人测试,Gson转比序列化效率高,这里不再测试,都没有自己重写clone效率高

image-20210414154756660

6. mapStrcuct

还有使用mapStruct实现clone的,这里不过多解释,对象中有list存储对象的,也不建议使用,下面是mapStrcut生成的代码,list类型的,它是通过new ArrayList()的方式,是浅拷贝

image-20210414155150236

7. 所有测试代码

package com.example.demo.domain.deepClone;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.demo.util.OrikaMapperUtils;
import org.apache.commons.lang3.SerializationUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StopWatch;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description:
 * @Author: zhaoyuan
 * @Date: 2021/4/14 0014
 * @Time 15:00
 */
public class TestDeepClone {

    public Parent parent;
    public StopWatch watch;

    @Before
    public void before() {
        parent = new Parent();
        parent.setId(1);
        parent.setName("parentName1");
        parent.setChild(new Child(1,"childName1"));

        Child child2 = new Child(2,"childName2");
        Child child3 = new Child(2,"childName3");
        Child child4 = new Child(2,"childName4");
        List<Child> children = new ArrayList<>();
        children.add(child2);
        children.add(child3);
        children.add(child4);
        parent.setChildren(children);

        List<List<Child>> childrenList = new ArrayList<>();
        Child child5 = new Child(2,"childName5");
        Child child6 = new Child(2,"childName6");
        Child child7 = new Child(2,"childName7");
        List<Child> children2 = new ArrayList<>();
        List<Child> children3 = new ArrayList<>();
        children2.add(child5);
        children2.add(child6);
        children3.add(child7);
        childrenList.add(children2);
        childrenList.add(children3);
        parent.setChildrenList(childrenList);

        watch = new StopWatch("clone");
    }

    @Test
    public void testClone() throws CloneNotSupportedException {
        watch.start();
        Parent clone = parent.clone();
        watch.stop();
        System.out.println(watch.prettyPrint());
        System.out.println(parent);
        System.out.println(clone);
        Assert.assertNotEquals(clone,parent);
        Assert.assertNotEquals(clone.getChild(),parent.getChild());
        Assert.assertNotEquals(clone.getChildren(),parent.getChildren());
        Assert.assertNotEquals(clone.getChildrenList(),parent.getChildrenList());
    }

    @Test
    public void testBeanUtilCopy(){
        watch.start();
        Parent clone = new Parent();
        BeanUtils.copyProperties(parent,clone);
        watch.stop();
        System.out.println(watch.prettyPrint());

        System.out.println(parent);
        System.out.println(clone);
        Assert.assertNotEquals(clone,parent);
        Assert.assertNotEquals(clone.getChild(),parent.getChild());
        Assert.assertNotEquals(clone.getChildren(),parent.getChildren());
        Assert.assertNotEquals(clone.getChildrenList(),parent.getChildrenList());
    }

    @Test
    public void testOrikaClone(){
        watch.start();
        Parent clone = OrikaMapperUtils.map(parent,Parent.class);
        watch.stop();
        System.out.println(watch.prettyPrint());

        System.out.println(parent);
        System.out.println(clone);
        Assert.assertNotEquals(clone,parent);
        Assert.assertNotEquals(clone.getChild(),parent.getChild());
        Assert.assertNotEquals(clone.getChildren(),parent.getChildren());
        Assert.assertNotEquals(clone.getChildrenList(),parent.getChildrenList());
    }

     /**
      * 需要类实现 Serializable
     */
      @Test
      public void testSerializationClone()  {
          watch.start();
          Parent clone = SerializationUtils.clone(parent);
          watch.stop();
          System.out.println(watch.prettyPrint());
        
          System.out.println(parent);
          System.out.println(clone);
          Assert.assertNotEquals(clone,parent);
          Assert.assertNotEquals(clone.getChild(),parent.getChild());
          Assert.assertNotEquals(clone.getChildren(),parent.getChildren());
          Assert.assertNotEquals(clone.getChildrenList(),parent.getChildrenList());
      }

  @Test
    public  void testJsonClone(){
      watch.start();
      JSONObject parse = JSONUtil.parseObj(parent);
      Parent clone = JSONUtil.toBean(parse, Parent.class);
      watch.stop();
      System.out.println(watch.prettyPrint());

      System.out.println(parent);
      System.out.println(clone);
      Assert.assertNotEquals(clone,parent);
      Assert.assertNotEquals(clone.getChild(),parent.getChild());
      Assert.assertNotEquals(clone.getChildren(),parent.getChildren());
      Assert.assertNotEquals(clone.getChildrenList(),parent.getChildrenList());
  }

}

8. 效率总结

数据来源:https://zhuanlan.zhihu.com/p/260117694

没有测试kryo序列化,看起来效率也还可以

 *  深度拷贝类型        循环次数[1000]      循环次数[10000]      循环次数[1000000]
 *  new               5 ms               14 ms              133 ms
 *
 *  Cloneable:        < 1 ms             7 ms               88 ms
 *
 *  Jdk序列化:         272 ms             1589 ms            66190 ms
 *
 *  Kryo序列化:        95 ms              123 ms             2438 ms
 *
 *  Json序列化:        1203 ms            3746 ms            163512 ms

总结: 1)、序列化性能 Clone > new > Kryo序列化 > Jdk序列化 > Json(各种Json类似)序列化
2)、Clone深拷贝性能最高,但是如果属性中有特定的对象字段,则需要自己编写代码
3)、new 性能仅次于Clone,因为需要执行Jvm过程(常量池判断,内存分配,值初始化,init方法调用,栈中对象的引用等),并且主要是每个对象需要单独编写代码,当然也不建议使用反射
4)、kryo 性能较高,并且不需要单独的开发, 若对性能不是特别高,可以考虑使用.(kryo是非线程安全的,项目中使用时可以放入ThreadLocal中)
5)、Jdk序列化和Json序列化,性能太低,高性能项目不建议使用

==如果性能要求特别高(或者对象结构层次不深),可以使用Clone方式;否则可以考虑使用 Kryo序列化和反序列化实现对象深拷贝==
原文地址:https://www.cnblogs.com/zhaoyuan72/p/14658341.html