Java 简单易用的深拷贝开源库(java深拷贝工具)

createh52个月前 (02-01)技术教程11

浅拷贝是指在对一个对象进行拷贝时,只拷贝对象本身和其中的基本数据类型,而不拷贝对象内部的引用类型。因此,在浅拷贝的对象中,引用类型的变量指向的依旧是原始对象中的引用。

深拷贝是指在对一个对象进行拷贝时,不仅拷贝对象本身和其中的基本数据类型,同时也拷贝对象内部的引用类型。因此,在深拷贝的对象中,引用类型的变量指向的是全新的对象。

cloning

克隆库是一个小型的开源(Apache许可)Java库,用于深度克隆对象。对象不必实现可克隆接口。实际上,这个库可以克隆任何Java对象。如果您不想修改缓存对象,或者每当您想创建对象的深度副本时,它可以在缓存实现中使用。

重要提示:Java类的深度克隆可能意味着数千个对象被克隆!另外,克隆文件和流可能会导致JVM崩溃。强烈建议在开发期间启用将克隆类转储到stdout,以便查看克隆的内容。

  
      io.github.kostaskougios
      cloning
      1.10.3
  

Github: https://github.com/kostaskougios/cloning

cloning 测试代码

cloning 可以深度克隆任何 java 对象, 简单对象,复杂对象和集合对象,集合中的每个元素也都会被深度克隆。简单易用,功能强大。

publicclass CloningExp {

    public static void main(String[] args) {
        Cloner cloner = new Cloner();

        // clone 简单对象
        Bean1 bean1 = new Bean1();
        bean1.setF1("XA");
        bean1.setF2(478354);
        System.out.println("bean1 -> " + bean1);

        Bean1 clonedBean1 = cloner.deepClone(bean1);
        System.out.println("clonedBean1 -> " + clonedBean1);
        System.out.println("bean1 == clonedBean1 -> " + (bean1 == clonedBean1));

        // clone 复杂对象
        Bean2 bean2 = new Bean2();
        bean2.setF1("VVV");
        bean2.setF2(bean1);
        System.out.println("bean2 -> " + bean2);

        Bean2 clonedBean2 = cloner.deepClone(bean2);
        System.out.println("clonedBean2 -> " + clonedBean2);
        System.out.println("bean2 == clonedBean2 -> " + (bean2 == clonedBean2));
        System.out.println("bean2.bean1 == clonedBean2.bean1 -> " + (bean2.getF2() == clonedBean2.getF2()));

        // clone 集合
        List bean1List = new ArrayList<>();
        bean1List.add(new Bean1("Bean1-1" , 1));
        bean1List.add(new Bean1("Bean1-2" , 1));
        bean1List.add(new Bean1("Bean1-3" , 1));
        System.out.println("bean1List -> " + bean1List);

        List clonedBean1List = cloner.deepClone(bean1List);
        System.out.println("clonedBean1List -> " + clonedBean1List);
        System.out.println("bean1List == clonedBean1List -> " + (bean1List == clonedBean1List));

        Map map = new HashMap<>();
        Bean1 mapBean1 = new Bean1("Bean1-1", 1);
        Bean2 mapBean2 = new Bean2("Bean2-1", mapBean1);
        map.put(mapBean1 , mapBean2);
        System.out.println("map -> " + map);

        Map clonedMap = cloner.deepClone(map);
        System.out.println("clonedMap -> " + clonedMap);
        System.out.println("map == clonedMap -> " + (map == clonedMap));

        Cloner clonedCloner = cloner.deepClone(cloner);
        System.out.println("cloner == clonedCloner -> " + (cloner == clonedCloner));

        Map sourceMap = new HashMap<>();
        sourceMap.put("1" , new Bean1("CAC" , 555));
        sourceMap.put("2" , new Bean1("AXC" , 5589));
        sourceMap.put("3" , new Bean1("bbv" , 76));
        sourceMap.put("4" , "dasfsdgsdg");
        sourceMap.put("5" , new Object());
        sourceMap.put("6" , new ArrayList<>());
        sourceMap.put("7" , 99999999999L);
        sourceMap.put("8" , new HashMap<>());

        long startTimeMillis = System.currentTimeMillis();
        for (int i = 0 ;i < 500000; i++) {
            cloner.deepClone(sourceMap);
        }
        System.out.println("time cost -> " + (System.currentTimeMillis() - startTimeMillis) + " ms");

    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    staticclass Bean1 {

        private String f1;

        private Integer f2;

    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    staticclass Bean2 {

        private String f1;

        private Bean1 f2;
    }
}
bean1 -> CloningExp.Bean1(f1=XA, f2=478354)
clonedBean1 -> CloningExp.Bean1(f1=XA, f2=478354)
bean1 == clonedBean1 -> false
bean2 -> CloningExp.Bean2(f1=VVV, f2=CloningExp.Bean1(f1=XA, f2=478354))
clonedBean2 -> CloningExp.Bean2(f1=VVV, f2=CloningExp.Bean1(f1=XA, f2=478354))
bean2 == clonedBean2 -> false
bean2.bean1 == clonedBean2.bean1 -> false
bean1List -> [CloningExp.Bean1(f1=Bean1-1, f2=1), CloningExp.Bean1(f1=Bean1-2, f2=1), CloningExp.Bean1(f1=Bean1-3, f2=1)]
clonedBean1List -> [CloningExp.Bean1(f1=Bean1-1, f2=1), CloningExp.Bean1(f1=Bean1-2, f2=1), CloningExp.Bean1(f1=Bean1-3, f2=1)]
bean1List == clonedBean1List -> false
map -> {CloningExp.Bean1(f1=Bean1-1, f2=1)=CloningExp.Bean2(f1=Bean2-1, f2=CloningExp.Bean1(f1=Bean1-1, f2=1))}
clonedMap -> {CloningExp.Bean1(f1=Bean1-1, f2=1)=CloningExp.Bean2(f1=Bean2-1, f2=CloningExp.Bean1(f1=Bean1-1, f2=1))}
map == clonedMap -> false
cloner == clonedCloner -> false
time cost -> 447 ms

cloning 性能

Map sourceMap = new HashMap<>();
sourceMap.put("1" , new Bean1("CAC" , 555));
sourceMap.put("2" , new Bean1("AXC" , 5589));
sourceMap.put("3" , new Bean1("bbv" , 76));
sourceMap.put("4" , "dasfsdgsdg");
sourceMap.put("5" , new Object());
sourceMap.put("6" , new ArrayList<>());
sourceMap.put("7" , 99999999999L);
sourceMap.put("8" , new HashMap<>());

对 sourceMap 克隆 50万次耗时 447 ms

Apache Commons Lang3

Apache Commons Lang3 中有一个 SerializationUtils 类用来对目标对象进行深度克隆,它内部使用的是 JDK 内置的对对象的序列化和反序列化,所以使用 SerializationUtils 有个前提是对象必须实现了 Serializable 接口。

 
    org.apache.commons
    commons-lang3
    3.13.0

SerializationUtils 测试代码

publicclass SerializationUtilsExp {

    public static void main(String[] args) {

        // clone 简单对象
        Bean1 bean1 = new Bean1();
        bean1.setF1("XA");
        bean1.setF2(478354);
        System.out.println("bean1 -> " + bean1);

        Bean1 clonedBean1 = SerializationUtils.clone(bean1);
        System.out.println("clonedBean1 -> " + clonedBean1);
        System.out.println("bean1 == clonedBean1 -> " + (bean1 == clonedBean1));

        // clone 复杂对象
        Bean2 bean2 = new Bean2();
        bean2.setF1("VVV");
        bean2.setF2(bean1);
        System.out.println("bean2 -> " + bean2);

        Bean2 clonedBean2 = SerializationUtils.clone(bean2);
        System.out.println("clonedBean2 -> " + clonedBean2);
        System.out.println("bean2 == clonedBean2 -> " + (bean2 == clonedBean2));
        System.out.println("bean2.bean1 == clonedBean2.bean1 -> " + (bean2.getF2() == clonedBean2.getF2()));

        // clone 集合
        ArrayList bean1List = new ArrayList<>();
        bean1List.add(new Bean1("Bean1-1" , 1));
        bean1List.add(new Bean1("Bean1-2" , 1));
        bean1List.add(new Bean1("Bean1-3" , 1));
        System.out.println("bean1List -> " + bean1List);

        List clonedBean1List = SerializationUtils.clone(bean1List);
        System.out.println("clonedBean1List -> " + clonedBean1List);
        System.out.println("bean1List == clonedBean1List -> " + (bean1List == clonedBean1List));

        HashMap map = new HashMap<>();
        Bean1 mapBean1 = new Bean1("Bean1-1", 1);
        Bean2 mapBean2 = new Bean2("Bean2-1", mapBean1);
        map.put(mapBean1 , mapBean2);
        System.out.println("map -> " + map);

        Map clonedMap = SerializationUtils.clone(map);
        System.out.println("clonedMap -> " + clonedMap);
        System.out.println("map == clonedMap -> " + (map == clonedMap));

        HashMap sourceMap = new HashMap<>();
        sourceMap.put("1" , new Bean1("CAC" , 555));
        sourceMap.put("2" , new Bean1("AXC" , 5589));
        sourceMap.put("3" , new Bean1("bbv" , 76));
        sourceMap.put("4" , "dasfsdgsdg");
        sourceMap.put("5" , "AAAAAAAAAAAAAA");
        sourceMap.put("6" , new ArrayList<>());
        sourceMap.put("7" , 99999999999L);
        sourceMap.put("8" , new HashMap<>());

        long startTimeMillis = System.currentTimeMillis();
        for (int i = 0 ;i < 500000; i++) {
            SerializationUtils.clone(sourceMap);
        }
        System.out.println("time cost -> " + (System.currentTimeMillis() - startTimeMillis) + " ms");
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    staticclass Bean1 implements Serializable {

        private String f1;

        private Integer f2;

    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    staticclass Bean2 implements Serializable {

        private String f1;

        private Bean1 f2;
    }
}

bean1 -> SerializationUtilsExp.Bean1(f1=XA, f2=478354)
clonedBean1 -> SerializationUtilsExp.Bean1(f1=XA, f2=478354)
bean1 == clonedBean1 -> false
bean2 -> SerializationUtilsExp.Bean2(f1=VVV, f2=SerializationUtilsExp.Bean1(f1=XA, f2=478354))
clonedBean2 -> SerializationUtilsExp.Bean2(f1=VVV, f2=SerializationUtilsExp.Bean1(f1=XA, f2=478354))
bean2 == clonedBean2 -> false
bean2.bean1 == clonedBean2.bean1 -> false
bean1List -> [SerializationUtilsExp.Bean1(f1=Bean1-1, f2=1), SerializationUtilsExp.Bean1(f1=Bean1-2, f2=1), SerializationUtilsExp.Bean1(f1=Bean1-3, f2=1)]
clonedBean1List -> [SerializationUtilsExp.Bean1(f1=Bean1-1, f2=1), SerializationUtilsExp.Bean1(f1=Bean1-2, f2=1), SerializationUtilsExp.Bean1(f1=Bean1-3, f2=1)]
bean1List == clonedBean1List -> false
map -> {SerializationUtilsExp.Bean1(f1=Bean1-1, f2=1)=SerializationUtilsExp.Bean2(f1=Bean2-1, f2=SerializationUtilsExp.Bean1(f1=Bean1-1, f2=1))}
clonedMap -> {SerializationUtilsExp.Bean1(f1=Bean1-1, f2=1)=SerializationUtilsExp.Bean2(f1=Bean2-1, f2=SerializationUtilsExp.Bean1(f1=Bean1-1, f2=1))}
map == clonedMap -> false
time cost -> 26061 ms

SerializationUtils 性能

HashMap sourceMap = new HashMap<>();
sourceMap.put("1" , new Bean1("CAC" , 555));
sourceMap.put("2" , new Bean1("AXC" , 5589));
sourceMap.put("3" , new Bean1("bbv" , 76));
sourceMap.put("4" , "dasfsdgsdg");
sourceMap.put("5" , "AAAAAAAAAAAAAA");
sourceMap.put("6" , new ArrayList<>());
sourceMap.put("7" , 99999999999L);
sourceMap.put("8" , new HashMap<>());

对 sourceMap 克隆 50万次耗时 25716 ms

其他一些深拷贝的方式

  1. new 对象 (太累了且不优雅)
  2. JSON 序列化
  3. 使用一些对象序列化库,例如:Protocol Buffers , Kryo , ProtoStuff , Hessian ;

相关文章

你知道Java的对象拷贝方式有哪几种吗?

【死记硬背】总共有四种,分别是直接赋值拷贝、浅拷贝、深拷贝和序列化。直接赋值拷贝:这个实际上复制的是对象的引用地址,如:Person p1 = p2,则p1和p2指向的是同一个对象的地址。因此,p1属...

你还在用BeanUtils进行对象属性拷贝?

在做业务的时候,为了隔离变化,我们会将DAO查询出来的DO和对前端提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的;但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代...

Java的深拷贝与浅拷贝详解(java的深拷贝与浅拷贝详解图)

前言拷贝,顾名思义,就是复制一个一模一样的东西。那么放到对象上,也就是复制一个一模一样的对象了。Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返...

java对象深拷贝的三种实现方式,可避免循环引用导致的堆栈溢出

在java编程中,难免要对一个对象进行复制,复制分为深拷贝和浅拷贝。浅拷贝只复制对象本身,对于对象引用的其他对象不进行复制。 深拷贝则将对象与引用对象,全部进行拷贝。最简单的深拷贝就是自己new一个对...

Java常用的几种属性拷贝工具类使用总结

Java属性拷贝工具类使用总结对项目中经常使用的属性拷贝工具类进行总结:org.apache.commons.beanutils.BeanUtilsorg.apache.commons.beanuti...

④ JAVA IO—拷贝(java拷贝文件夹到另外一个文件夹)

一、 传统的IO1. 数据由磁盘拷贝到内核空间(DMA),再由内核空间拷贝到用户空间(JVM)2. 用户可能会对拷贝进来的数据进行操作3. 数据从用户空间拷贝到内核空间(JVM),再通过内核空间将数据...