• Java对象的拷贝与克隆


    Java对象的拷贝与克隆

        在日常开发中,我们经常需要给对象进行赋值,通常会调用其 set/get 方法,有些时候,为了简化代码,我们会采用第三方工具类进行属性拷贝。但是面对如此多的拷贝工具和方法,其性能差异如何不得而知,下面我就对几种属性拷贝工具和方法进行性能分析。

        比如我们经常在代码中会对一个数据结构封装成 DO、SDO、DTO、VO 等,而 这些 Bean 中的大部分属性都是一样的,所以使用属性拷贝类工具可以帮助我们节省 大量的 set 和 get 操作。

    属性拷贝

        我所知道的属性拷贝方法大致分为三种:1.原生get/set方法 2.属性拷贝工具类 3. 序列化再反序列化

    1. set/get方法这里就不做介绍了。

    2. 属性拷贝工具类。

      目前使用比较广泛的属性拷贝工具类有:

      1. Spring BeanUtils

      2. Apache BeanUtils
        还有其他属性拷贝的工具类这里就不再赘述了,原理都差不多。只不过实现的功能有所差异。

        性能对比:

        首先定义两个类:

        	@Data
        	public static class Student {
        		private Integer id;
        		private String name;
        		private Integer age;
        		private Date birthday;
        	}
        
        	@Data
        	public static class StudentDTO {
        		private String name;
        		private Integer age;
        		private Date birthday;
        	}
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14

        进行拷贝测试性能:

        	public static void main(String[] args) {
        		Student student = new Student();
        		student.setId(1);
        		student.setName("独孤求败");
        		student.setAge(18);
        		student.setBirthday(new Date());
        		
                StopWatch watch = new StopWatch();
        		watch.start();
        		
                int size = 1000000;
        		for (int i = 0; i < size; i++) {
        			StudentDTO dto = new StudentDTO();
        			BeanUtils.copyProperties(student, dto);
        		}
        		watch.stop();
        		System.out.println(watch.prettyPrint());
        	}
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18

        结果如下:

        工具类执行1000次耗时执行10000次耗时执行100000次耗时执行1000000次耗时执行10000000次耗时
        Spring BeanUtils132ms178ms386ms2315ms18976ms
        Apache BeanUtils140ms314ms699ms4399ms40302ms

        结论:

        ​ 由此可见,Sping的属性拷贝工具类是最快的,因为 Apache BeanUtils 力求做得完美 , 在代码中增加了非常多的校验、兼容、日志打印等代码,过度的包装导致性能下降严重。如果是追求性能的话建议不要使用Apache BeanUtils

    3. JSON序列化再反序列化实现拷贝

      序列化和反序列化也能实现拷贝和克隆的功能,常见的JSON序列化方式有:

      • 阿里的Fastjson

      • Google的Gson

        进行拷贝功能测试:

        	public static void main(String[] args) {
        		Student student = new Student();
        		student.setId(1);
        		student.setName("独孤求败");
        		student.setAge(18);
        		student.setBirthday(new Date());
        		StopWatch watch = new StopWatch();
                Gson gson = new Gson();
                
        		watch.start();
        		int size = 1000;
        		for (int i = 0; i < size; i++) {
                    
        			StudentDTO dto = JSON.parseObject(JSON.toJSONString(student), StudentDTO.class);
                    
                    // StudentDTO dto = gson.fromJson(gson.toJson(student), StudentDTO.class);
                    
        		}
        		watch.stop();
            }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20

        结果如下:

        序列化工具执行1000次耗时执行10000次耗时执行100000次耗时执行1000000次耗时执行10000000次耗时
        Fastjson94ms121ms195ms691ms5684ms
        Gson81ms251ms1073ms7867ms72722ms

        结论:

        ​ 可以看出,阿里的Fastjson表现还是不错的,当数据量非常大时,Fastjson与Gson的效率有非常明显的差别。但是,Fastjson漏洞频发,导致使用Fastjson的公司不得不经常加班修复漏洞,希望后面Fastjson漏洞能少一点吧。

      最后从整体上看一下两类属性拷贝的性能差距

      工具类执行1000次耗时执行10000次耗时执行100000次耗时执行1000000次耗时执行10000000次耗时
      Spring BeanUtils132ms178ms386ms2315ms18976ms
      Apache BeanUtils140ms314ms699ms4399ms40302ms
      Fastjson94ms121ms195ms691ms5684ms
      Gson81ms251ms1073ms7867ms72722ms

      ​ 从下图中能更加直观的看出几种属性拷贝的执行效率差别。不过,最高效的还是直接调用get/set方法,所谓的大道至简应该就是这个道理吧。

    在这里插入图片描述

    对象克隆(拷贝)

    在Java语言中,拷贝一个对象时,有浅拷贝与深拷贝两种。

    浅拷贝:只拷贝源对象的地址,所以新对象与老对象共用一个地址,当该地址变化时,两个对象也会随之改变。

    深拷贝:拷贝对象的所有值,即使源对象发生任何改变,拷贝的值也不会变化。

    浅拷贝这里就不介绍了,主要介绍一下几种深拷贝方式。常用的深拷贝方式有几种:

    1. 重写Object中的clone()方法
    2. Apache Commons Lang序列化
    3. JSON序列化

    重写clone

    ​ 调用重写的Object中的clone()方法,实际上是调用C++的本地函数进行拷贝,所以其拷贝效率非常高。但是有几点需要注意。

    • 拷贝对象需要实现Cloneable接口,并重写clone()方法。

    • 拷贝对象的属性如果是对象乐行,拷贝的仍然是原属性对象。

      比如:

      	@Data
      	public static class Student implements Serializable, Cloneable {
      		private Integer id;
      		private String name;
      		private Integer age;
      		private Date birthday;
      
      		@Override
      		public Student clone() {
      			try {
      				Student clone = (Student) super.clone();
      				// TODO: copy mutable state here, so the clone can't change the internals of the original
      				return clone;
      			} catch (CloneNotSupportedException e) {
      				throw new AssertionError();
      			}
      		}
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      ​拷贝之后,新的对象中的属性对象仍然是原对象中的属性对象。所以对象属性需要特殊处理。但是,有些对象不用处理,比如String和基本类型的包装类,因为他们都是不可变类,每次进行运算时都会指向新的对象,所以不用担心修改后会影响克隆对象的属性值。

    Apache Commons Lang序列化

    ​ Apache Commons Lang序列化主要使用org.apache.commons.lang3包下SerializationUtils.clone()方法进行序列化克隆。序列化克隆后生成的属性对象都是新的,与原对象没有关系。此种方法也有几点需要注意:

    • 克隆对象需要实现Serializable接口。

    • 序列化效率比较低。

      	public static void main(String[] args) {
              
      		Student student = new Student();
      		student.setId(1);
      		student.setName("独孤求败");
      		student.setAge(18);
      		student.setBirthday(new Date());
      
      		Student student1 = SerializationUtils.clone(student);
      		System.out.println(student1);
      
      	}
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

    JSON序列化

    ​ JSON序列化通过把对象序列化为json字符串,再把json字符串反序列化成对象实现。上面属性拷贝的时候已经讲过了,主流的JSON工具有阿里的FastJson和Google的Gson,这里就不再赘述。

    下面通过简单的测试验证几种克隆的效率:

    public static void main(String[] args) {
    		Student student = new Student();
    		student.setId(1);
    		student.setName("独孤求败");
    		student.setAge(18);
    		student.setBirthday(new Date());
    		StopWatch watch = new StopWatch();
    		Gson gson = new Gson();
    
    		watch.start();
    		for (int i = 0; i < 100000; i++) {
    			Student student1 = student.clone();
             // Student student1 = SerializationUtils.clone(student);
             // Student student1 = gson.fromJson(gson.toJson(student), Student.class);
             // Student student1 = JSON.parseObject(JSON.toJSONString(student), Student.class);
    		}
    		watch.stop();
    		System.out.println(watch.getTotalTimeMillis() + "ms");
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    测试结果:

    拷贝方法执行1000次执行10000次执行100000次执行1000000次执行10000000次
    原生clone0ms0ms4ms14ms75ms
    Apache SerializationUtils96ms320ms1148ms7880ms76159ms
    Gson111ms315ms1069ms7345ms70489ms
    FastJson113ms121ms203ms514ms3552ms

    1 - Made with DesignCap

        综上所述,原生clone和FastJson序列化方式进行拷贝比较好,如果对象属性是基本类型或者String和基本类型的包装类这种不可变类,就可以用原生clone方法进行克隆,否则则可以用FastJson序列化再反序列化进行克隆。当然,上面讲到的属性拷贝也可以用,但是性能却没有那么好。

  • 相关阅读:
    B/S架构,java源码,医院绩效管理系统,覆盖了医院绩效管理工作“PDCA”循环的全过程,支持二次开发
    Python 教程之控制流(12)组合迭代器
    一个Linux自动备份脚本的示例
    一个基于.Net Core遵循Clean Architecture原则开源架构
    软件测试人员迷茫之中如何找到职业发展的方向?
    我的周刊(第057期)
    R-install_miniconda()卸载 | conda命令行报错及解决方法
    【数据挖掘】期末复习(样卷题目+少量知识点)
    IBM Spectrum LSF Explorer 为要求苛刻的分布式和任务关键型高性能技术计算环境提供强大的工作负载管理
    Git——入门介绍
  • 原文地址:https://blog.csdn.net/pig_boss/article/details/134265032