JAVA对象拷贝该使用哪种方式?

2019-11-21  本文已影响0人  琥珀光

背景

在Java中会遇到很多对象拷贝的情况,用的时候比较随意,一般直接使用Beautils的copy方法,图简单方便,但是经过测试后发现实际效率真的千差万别

众所周知,拷贝分为浅拷贝和深拷贝,我认为浅拷贝并不是真正意义的拷贝,所以本文的对象拷贝均为深拷贝

如果想直接看结论,直接滑动到底部

拷贝方式

Java对象拷贝目前已经的方式有四种方式:

实施测试

该选择哪种?本文用了JMH压测工具做了比较,为了避免简单对象影响测试结果,我使用了稍微复杂点的对象做了测试,至于JMH如何使用,参考JMH: 最装逼,最牛逼的基准测试工具套件

测试对象的设计是这样的:里面有一些基本类型,最重要的是有一个对象,对象里面又是一个复合对象,不算复杂,也并不简单,满足日常需求的情况

测试对象如下:

package org.sample;

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

/**
 * Default Note
 *
 * @author liupz@glodon.com
 * @version 1.0
 * @date 2019/11/21 16:59
 */
public class ComplexObject implements Cloneable{
    private int number;


    private String string;


    private long time;


    private short st;


    private double dd;


    private Date date;

    private Person person;


    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public short getSt() {
        return st;
    }

    public void setSt(short st) {
        this.st = st;
    }

    public double getDd() {
        return dd;
    }

    public void setDd(double dd) {
        this.dd = dd;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    public static class Person implements Cloneable{
        private int age;
        private String username;
        private String password;

        private List<Person> sons;

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public List<Person> getSons() {
            return sons;
        }

        public void setSons(List<Person> sons) {
            this.sons = sons;
        }
        @Override
        public Object clone() {

            Person person = null;
            try{
                person = (Person)super.clone();   //浅复制
            }catch(CloneNotSupportedException e) {
                e.printStackTrace();
            }
            person.sons = new ArrayList<>();
            for(Person person1:sons){
                ComplexObject.Person s1 = new ComplexObject.Person();
                s1.setSons(person1.getSons());
                s1.setAge(person1.getAge());
                s1.setPassword(person1.getPassword());
                s1.setUsername(person1.getUsername());

                person.sons.add(s1);
            }

            return person;
        }

    }

    @Override
    public Object clone() {

        ComplexObject complexObject = null;
        try{
            complexObject = (ComplexObject)super.clone();   //浅复制
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        complexObject.person = (Person) person.clone();   //深度复制
        return complexObject;
    }
}

对于压测类,我在压测类构建的时候初始化了一个复杂对象,然后在压测方法中测试了四种情况,压测类的代码如下:

/*
 * Copyright (c) 2014, Oracle America, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 *  * Neither the name of Oracle nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.sample;

import org.openjdk.jmh.annotations.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class MyBenchmark {

    private ComplexObject complexObject;

    /*序列化方式的时候放开注释*/
//    private ComplexObjectSerializer complexObject;


    public MyBenchmark(){
        complexObject = new ComplexObject();

        complexObject.setDate(new Date());
        complexObject.setDd(1111);
        complexObject.setNumber(222);
        complexObject.setSt((short) 1.0);
        complexObject.setString("XXXXXXXXXXXX");

        complexObject.setTime(System.currentTimeMillis());

        List<ComplexObject.Person> sons = new ArrayList<>();
        ComplexObject.Person  s1 = new ComplexObject.Person();
        s1.setAge(11);
        s1.setPassword("XXXXXXXXXXXXXXX");
        s1.setUsername("AAAAAAAAAAAAAAAAA");
        s1.setSons(null);
        sons.add(s1);


        ComplexObject.Person  s2 = new ComplexObject.Person();
        s2.setAge(11);
        s2.setPassword("XXXXXXXXXXXXXXX");
        s2.setUsername("AAAAAAAAAAAAAAAAA");
        s2.setSons(null);
        sons.add(s2);

        ComplexObject.Person  s3 = new ComplexObject.Person();
        s3.setAge(11);
        s3.setPassword("XXXXXXXXXXXXXXX");
        s3.setUsername("AAAAAAAAAAAAAAAAA");
        s3.setSons(null);
        sons.add(s3);


        ComplexObject.Person  person = new ComplexObject.Person();
        person.setAge(11);
        person.setPassword("XXXXXXXXXXXXXXX");
        person.setUsername("AAAAAAAAAAAAAAAAA");
        person.setSons(sons);

        complexObject.setPerson(person);
    }

    @Benchmark
    public void testMethod() {
        // This is a demo/sample template for building your JMH benchmarks. Edit as needed.
        // Put your benchmark code here.
//        Student stu1 = new Student();
//        stu1.setNumber(12345);
//        Student stu2 = new Student();
//        try {
//            BeanUtils.copyProperties(stu1, stu2);
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//        } catch (InvocationTargetException e) {
//            e.printStackTrace();
//        }
        /**
         * 1.通过set方法拷贝对象
         */
//        ComplexObject complexObjects = new ComplexObject();
//
//        ComplexObject.Person person = new ComplexObject.Person();
//        person.setUsername(complexObject.getPerson().getUsername());
//        person.setPassword(complexObject.getPerson().getPassword());
//        person.setAge(complexObject.getPerson().getAge());
//
//        List<ComplexObject.Person> sons = new ArrayList<>();
//
//        for(ComplexObject.Person son:complexObject.getPerson().getSons()){
//            ComplexObject.Person s1 = new ComplexObject.Person();
//            s1.setSons(son.getSons());
//            s1.setAge(son.getAge());
//            s1.setPassword(son.getPassword());
//            s1.setUsername(son.getUsername());
//            sons.add(s1);
//        }
//
//        person.setSons(sons);
//
//
//        complexObjects.setPerson(person);
//        complexObjects.setTime(complexObject.getTime());
//        complexObjects.setString(complexObject.getString());
//        complexObjects.setSt(complexObject.getSt());
//        complexObjects.setDd(complexObject.getDd());
//        complexObjects.setNumber(complexObject.getNumber());
//        complexObjects.setDate(complexObject.getDate());


        /**
         * 克隆方式
         */
//        ComplexObject complexObjects = (ComplexObject) complexObject.clone();


        /**
         * BeanUtils方式
         */
//        ComplexObject complexObjects = new ComplexObject();
//        try {
//            BeanUtils.copyProperties(complexObjects,complexObject);
//        } catch (IllegalAccessException e) {
//            e.printStackTrace();
//        } catch (InvocationTargetException e) {
//            e.printStackTrace();
//        }

        /**
         * 序列化深拷贝
         */
        ComplexObject complexObjects = (ComplexObject)complexObject.clone();


    }

}

测试结果

分别修改压测方法,得到的测试结果如下:

拷贝方式 测试结果
Bean对象的Setter方式
继承覆盖clone方法
BeanUtils方式
Java本身序列化方式

由此可以看出:
Bean对象的Setter方式最优,Java本身序列化方式大概是Setter方式 的500倍! BeanUtils方式 也非常低效,大概是Setter方式的100多倍,所以以前为了方便而直接使用BeanUtils的方式可以在效率不敏感的代码中使用,但是绝不能在高频程序中用,最后看一下继承覆盖clone方法,与set方式相差无几,但是set方式几乎是用到的时候都要写一遍,clone方式只需要实现cloneable接口就行,只写一次就能在任何用到这个对象拷贝的地方使用

结论

所以最终结论:千万别为了省事使用第三方库,除非你的程序不是高频调用的,强烈建议自己实现clone方法实现对象拷贝

参考资料

Java对象的复制三种方式

JMH: 最装逼,最牛逼的基准测试工具套件

Java对象的快速复制的几种方式

上一篇下一篇

猜你喜欢

热点阅读