背景
在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方法实现对象拷贝
网友评论