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 ...
随机推荐
- 29-ESP8266 SDK开发基础入门篇--编写TCP 客户端程序(Lwip RAW模式,非RTOS版,精简入门)
https://www.cnblogs.com/yangfengwu/p/11456667.html 由于上一节的源码长时间以后会自动断开,所以再做这一版非RTOS版的,咱直接用lua源码里面别人写的 ...
- sublime text 3插件改造之添加从模版新增文件到指定目录
简介:以前使用ST2里面的Sublime NFFT插件比较顺手,最近安装了ST3,但是Sublime NFFT插件不支持ST3,就下载了SublimeTmpl从模版新建文件插件.在使用时,习惯在侧边栏 ...
- 初试angularjs动画(animate)
angularjs不同版本的代码写法各有千秋,动画模块的写法也各有不同,以下是收集到的两大版本的写法,各位请: angularjs1.1.5版本(1.2之前) index.html代码: <!D ...
- Java为什么没有指针
为了摒弃指针带来的风险(当然了,也就放弃了指针带来的效率). 1.C/C++为什么有指针? 这个很简单,程序都是在内存中运行的,只要有内存,就有内存地址,有地址,就必然有指针,只是C++对内存地址的访 ...
- Linux进阶之Shell编程
1.什么是Shell Shell是一个命名行解释器,它为用户提供一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动.挂起.停止甚至是编写一些程序. 2.Shell脚本 ...
- THOR: Tracking Holistic Object Representations
THOR: Tracking Holistic Object Representations BMVC 2019 Oral 2019-08-04 10:30:09 Paper: https://arx ...
- python list 和 tuple详解
list------------------------------------------------------------------------ Python内置的一种数据类型是列表:list ...
- [转]Maven 国内源配置(2019/2/14)
原文地址:https://segmentfault.com/a/1190000018147680 新年开工后要开始新的项目,但是发现一些项目的依赖没有在阿里仓库Central或Public源之中,项目 ...
- springMVC中controller的传参的几种案例
1.springmvc的controller方法不指定method时,默认get/post都支持 //@RequestMapping(value="test") //@Reques ...
- Linux云主机(离线)搭建本地yum源
下载CentOS镜像 http://mirror.nsc.liu.se/centos-store/7.3.1611/isos/x86_64/ 离线yum源的配置 1.上传下载的镜像源iso 2.挂载i ...