深拷贝与浅拷贝
浅拷贝
介绍
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
特点
简而言之,当浅拷贝的对象是基本类型时,和复制没有太大的区别,但是如果浅拷贝的对象是一个引用,比如将POJO_A拷贝给POJO_B,使用BeanUtils.copyProperties(POJO_A, POJO_B),如果POJO_A的值发生改变,那POJO_B的值也会变。这个特性在日常正常使用时不会造成什么影响,而且拷贝引用的话可以节省空间。
问题
有一些特殊情况会出现问题,比如说下方这段代码。
void contextLoads() {
List<UsersInfo> userList = userInfoMapper.selectList(null);
System.out.println(("----- true data ------"));
userList.forEach(System.out::println);
UsersInfo wrongUserInfo = new UsersInfo();
List<UsersInfo> wrongUserList = new ArrayList<>();
for (UsersInfo usersInfo : userList) {
BeanUtils.copyProperties(usersInfo, wrongUserInfo);
wrongUserList.add(wrongUserInfo);
}
System.out.println(("----- wrong data ------"));
wrongUserList.forEach(System.out::println);
}
我的预期结果是返回true data中的五组数据,但是实际结果是得到了wrong data 中的五组一样的数据。
出现这种问题是因为我们使用了BeanUtils.copyProperties()方法,这个方法在底层是一个浅拷贝,它只是拷贝了对象的引用,所以随着usersInfo的变化,引用也一直在变,usersInfo的最后一次引用是Billie的数据,所以在之前的数据也都会变成最后一条数据。具体过程如下图,wrongUserInfo里的东西会随着userInfo的变化而变化,导致wrongUserInfoList里的数据也发生了变化。
如果要解决这里的问题,就要用到深拷贝了
深拷贝
介绍
深拷贝是在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝
特点
- 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
- 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
实现
在Java中,深拷贝的实现有多种方式,我用的方式是使用Gson转为Json数据再转回对象实现深拷贝,代码如下:
@Test
void contextLoads() {
List<UsersInfo> userList = userInfoMapper.selectList(null);
System.out.println(("----- true data ------"));
userList.forEach(System.out::println);
UsersInfo wrongUserInfo;
List<UsersInfo> wrongUserList = new ArrayList<>();
for (UsersInfo usersInfo : userList) {
Gson gson = new Gson();
wrongUserInfo = gson.fromJson(gson.toJson(usersInfo), UsersInfo.class);
wrongUserList.add(wrongUserInfo);
}
System.out.println(("----- wrong data ------"));
wrongUserList.forEach(System.out::println);
}
结果如下:
这样我们就修复了使用浅拷贝所出现的问题,但是在日常开发中,还是建议使用浅拷贝,因为深拷贝比浅拷比速度慢并且花销较大。