Java对象深拷贝浅拷贝总结
在java开发的过程中我们很多时候会有深拷贝需求,比如将一个请求体拷贝多次,修改成多个不同版笨,分别发给不同的服务,在比如维护不同的缓存时。还有些时候并不需要深拷贝,只是简单的类型转换,比如到将do对象转换为dto对象返回给前端,其中两者的字段基本相同,只是类名不一样。本文主要罗列了下自己总结的拷贝方式和适合的场景(深浅拷贝原理文章很多,本文不再解释)。
拷贝过程中用到的Bean定义:
@Data
public class Source {
String a;
Filed1 filed1;
Filed1 filed2;
List<Filed1> fileds;
@NoArgsConstructor
@AllArgsConstructor
@Data
public static class Filed1 {
String id;
}
}
深拷贝
1. 手动new
Source source = getSource();
Source target = new Source();
target.setFiled1(new Source.Filed1(source.getFiled1().getId()));
target.setFiled2(new Source.Filed1(source.getFiled2().getId()));
if (source.getFileds() != null) {
ArrayList<Source.Filed1> fileds = new ArrayList<>(source.getFileds().size());
for (Source.Filed1 filed : source.getFileds()) {
fileds.add(new Source.Filed1(filed.getId()));
}
target.setFileds(fileds);
}
手动new非常简单,但是非常繁琐不利于后期的维护,每次修改类定义的时候需要修改相应的copy方法,不过性能非常高。
2. clone方法
// Source类
public Source clone() {
Source clone = null;
try {
clone = (Source) super.clone();
clone.setFiled1(filed1.clone());
clone.setFiled2(filed2.clone());
//列表的克隆
if (fileds != null) {
ArrayList<Filed1> target = new ArrayList<>(this.fileds.size());
for (Filed1 filed : this.fileds) {
target.add(filed.clone());
}
clone.setFileds(target);
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
// Filed1类
public Filed1 clone() throws CloneNotSupportedException {
return (Filed1) super.clone();
}
在重写clone方法的时候,如果类的字段类型是String和Integer等不可变类型,那么source实例对应的字段是可以复用的,以为这个字段值不能被修改。如果字段类型是可变类型则也需要重写,如Source中Filed1字段类型不是不可变类型,则也需要重写clone方法,另外注意重写clone方法的类必须实现Cloneable类(public class Source implements Serializable),否则会抛出CloneNotSupportedException。
3. java自带序列化
ByteArrayOutputStream out = new ByteArrayOutputStream();
new ObjectOutputStream(out).writeObject(source);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
Source target = (Source) in.readObject();
// spring中封装了下可以直接使用
// Source target = (Source) SerializationUtils.deserialize(SerializationUtils.serialize(source));
这个方法很多书中都有提起,因为序列化来实现深度拷贝代码比较简单,可扩展性好,后期添加字段无需修改实现,不过类需要继承标记接口Serializable(public class Source implements Serializable)。不过这个方法没有什么实际用途,因为确实性能非常低。
4. json序列化
public class JsonCopy {
private static ObjectMapper mapper = new ObjectMapper();
public static String encodeWithoutNull(Object obj) throws Exception {
return mapper.writeValueAsString(obj);
}
public static <T> T decodeValueIgnoreUnknown(String str, Class<T> clazz) throws Exception {
return mapper.readValue(str, clazz);
}
// 一千万次 15.3秒
public static <T> T copy(T source, Class<T> tClass) throws Exception {
return decodeValueIgnoreUnknown(encodeWithoutNull(source), tClass);
}
}
一个简单的工具类,利用了Jackson库,性能一般,不过扩展性好,比java自带序列化很大提升。
性能测试
我在自己的机器上用每种方法实现Source对象的一千万次拷贝,测试了时间。结果如下:
| 类型 | 测试结果 |
|---|---|
| 手动new | 一千万次 774毫秒 |
| clone方法 | 一千万次 827毫秒 |
| java自带序列化 | 一千万次 109.7秒 |
| json序列化 | 一千万次 15.3秒 |
深拷贝总结
从可扩展性和性能方面的考虑,如果注重性能,那么使用手动new和clone方法,如果注重扩展性那么使用java自带序列化和json序列化。平时的使用中,优先使用json序列化,因为大部分场景下cpu不是瓶颈,在一些热点代码中改用重写clone方法。使用clone方法和手动New两个性能和可维护性都类似,只不过看你的喜好,我是认为clone比较符合Java风格,将对象的clone方法写在那个类中。
浅拷贝
1. spring BeanUtils(Apache BeanUtils)
Source source = getSource();
Source target = new Source();
BeanUtils.copyProperties(source, target);
spring的BeanuUtils和Apache BeanUtils原理都类似,都是利用反射获取了对象的字段,逐个赋值,性能方面其实也是比较好了,虽然利用了反射,但是内部缓存了反射的结果,后面在复制的时候可以直接取缓存的结果。反射的性能损耗在获取Class信息那一块,在调用的开销和普通调用的类似,Jvm也会使用Jit进行优化。
2. mapstruct
@Mapper
public interface SourceMapper {
SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
Source copy(Source car);
}
Source target = SourceMapper.INSTANCE.copy(source);
mapstruct和lombok的原理类型,在编译期根据你的注解生成所需要的方法,所以他的性能理论上和手写是一样的,现在springboot也可以和他很好的结合,如果遇到了对象拷贝的性能瓶颈可以考虑用下这个类库。不过遗憾的是他并不支持深拷贝。https://github.com/mapstruct/mapstruct/issues/695
性能测试
| 类型 | 测试结果 |
|---|---|
| BeanUtils | 一千万次 1825毫秒 |
| mapstruct | 一千万次 235毫秒 |
浅拷贝总结
浅拷贝也可以看到可以复制不同对象的实例字段,这是序列化和Clone方法等不具备的优势,在转化Bean的时候十分有用。在一般情况下,推荐使用Spring的BeanUtils类,不用引入额外的依赖,性能也够用。如果在高并发的场景下,可以考虑通过mapstruct进行优化,两者会有一个数量级的差距。
Java对象深拷贝浅拷贝总结的更多相关文章
- Java基础 深拷贝浅拷贝
Java基础 深拷贝浅拷贝 非基本数据类型 需要new新空间 class Student implements Cloneable{ private int id; private String na ...
- Java对象的浅拷贝和深拷贝&&String类型的赋值
Java中的数据类型分为基本数据类型和引用数据类型.对于这两种数据类型,在进行赋值操作.方法传参或返回值时,会有值传递和引用(地址)传递的差别. 浅拷贝(Shallow Copy): ①对于数据类型是 ...
- 什么是 Java 对象深拷贝?面试必问!
点击上方蓝色链接,关注并"设为星标" Java干货,每天及时推送 介绍 在Java语言里,当我们需要拷贝一个对象时,有两种类型的拷贝:浅拷贝与深拷贝. 浅拷贝只是拷贝了源对象的地址 ...
- java中深拷贝浅拷贝简析
Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...
- Map拷贝 关于对象深拷贝 浅拷贝的问题
问题:map拷贝时发现数据会变化. 高能预警,你看到的下面的栗子是不正确的,后面有正确的一种办法,如果需要看的话的,请看到底,感谢各同学的提醒,已做更正,一定要看到最后 先看例子: ...
- Java clone() 方法克隆对象——深拷贝与浅拷贝
基本数据类型引用数据类型特点 1.基本数据类型的特点:直接存储在栈(stack)中的数据 2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指 ...
- java对象的克隆以及深拷贝与浅拷贝
一.为什么要使用克隆 在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也 ...
- [改善Java代码]避免对象的浅拷贝
建议43: 避免对象的浅拷贝 我们知道一个类实现了Cloneable接口就表示它具备了被拷贝的能力,如果再覆写clone()方法就会完全具备拷贝能力.拷贝是在内存中进行的,所以在性能方面比直接通过ne ...
- 【转】JAVA中的浅拷贝和深拷贝
原文网址:http://blog.bd17kaka.net/blog/2013/06/25/java-deep-copy/ JAVA中的浅拷贝和深拷贝(shallow copy and deep co ...
随机推荐
- 浅谈SPFA判负环
目录 SPFA判负环 [前言] [不可代替性] [具体实现] SPFA的过程 判负环 [核心代码] [例题] SPFA判负环 有不足的地方请指出 本蒟蒻一定会修改吼 [前言] 最短路的求法中最广为人知 ...
- 分类模型的性能评价指标(Classification Model Performance Evaluation Metric)
二分类模型的预测结果分为四种情况(正类为1,反类为0): TP(True Positive):预测为正类,且预测正确(真实为1,预测也为1) FP(False Positive):预测为正类,但预测错 ...
- Redis BGSAVE因为内存不足 fork 失败导致目标 Redis 无法访问的问题
中秋的时候正在外面愉快的在外卖喝着咖啡玩电脑......突发 redis 报警从 sentry 应用端曝出的错误 MISCONF Redis is configured to save RDB sna ...
- 【洛谷】P4198 楼房重建(线段树)
传送门 分析 被线段树按在地上摩擦 先把左边转化成斜率,那么这个题就转化成每次修改一个点的值,输出前缀最大值的个数 看到标签是线段树,所以还是想想线段树的做法吧 既然是线段树,那么就要将区间分成两半 ...
- 【2019.11.20】SDN上机第4次作业
安装OpenDayLight控制器 配置JAVA环境 https://www.opendaylight.org/ 在官网进行下载OpenDayLight控制器 启动OpenDayLight控制器和安装 ...
- 关于js中onload事件的部分报错。
当使用onload获取元素时,建议在onload事件之前定义需要获取的元素名称,在onload里面只执行获取操作,这样获取到的元素在后面才能顺利使用. <!DOCTYPE html> &l ...
- Monkey框架(基础知识篇) - monkey启动与参数介绍
一.monkey启动 直接PC启动:> adb shell monkey [options] <count> shell 端启动:> adb shell >monkey ...
- 2019软工实践_Alpha(事后诸葛亮)
组长博客 感谢组长 总结思考 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 弥补Powerpoint中模板转换存在的缺陷,完善PPT模板一键转换的功能 ...
- Learning Context Graph for Person Search
Learning Context Graph for Person Search 2019-06-24 09:14:03 Paper:http://openaccess.thecvf.com/cont ...
- PostgreSQL中的索引(一)
引言 这一系列文章主要关注PostgreSQL中的索引. 可以从不同的角度考虑任何主题.我们将讨论那些使用DMBS的应用开发人员感兴趣的事项:有哪些可用的索引:为什么会有这么多不同的索引:以及如何使用 ...