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

背景

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

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

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

拷贝方式

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

  • Bean对象的Setter方式
  • 继承覆盖clone方法
  • BeanUtils方式
  • 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对象的快速复制的几种方式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355