Java如何对一个对象进行深拷贝?

Posted by Wudashan on October 14, 2018

深拷贝实现代码:https://github.com/wudashan/java-deep-copy

介绍

在Java语言里,当我们需要拷贝一个对象时,有两种类型的拷贝:浅拷贝与深拷贝。浅拷贝只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化。而深拷贝则是拷贝了源对象的所有值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。如下图描述:

了解了浅拷贝和深拷贝的区别之后,本篇博客将教大家几种深拷贝的方法。


拷贝对象

首先,我们定义一下需要拷贝的简单对象。

/**
* 用户
*/
public class User { private String name;
private Address address; // constructors, getters and setters } /**
* 地址
*/
public class Address { private String city;
private String country; // constructors, getters and setters }

如上述代码,我们定义了一个User用户类,包含name姓名,和address地址,其中address并不是字符串,而是另一个Address类,包含country国家和city城市。构造方法和成员变量的get()、set()方法此处我们省略不写。接下来我们将详细描述如何深拷贝User对象。


方法一 构造函数

我们可以通过在调用构造函数进行深拷贝,形参如果是基本类型和字符串则直接赋值,如果是对象则重新new一个。

测试用例

@Test
public void constructorCopy() { Address address = new Address("杭州", "中国");
User user = new User("大山", address); // 调用构造函数时进行深拷贝
User copyUser = new User(user.getName(), new Address(address.getCity(), address.getCountry())); // 修改源对象的值
user.getAddress().setCity("深圳"); // 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }

方法二 重载clone()方法

Object父类有个clone()的拷贝方法,不过它是protected类型的,我们需要重写它并修改为public类型。除此之外,子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。

重写代码

让我们修改一下User类,Address类,实现Cloneable接口,使其支持深拷贝。

/**
* 地址
*/
public class Address implements Cloneable { private String city;
private String country; // constructors, getters and setters @Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
} }
/**
* 用户
*/
public class User implements Cloneable { private String name;
private Address address; // constructors, getters and setters @Override
public User clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.setAddress(this.address.clone());
return user;
} }

需要注意的是,super.clone()其实是浅拷贝,所以在重写User类的clone()方法时,address对象需要调用address.clone()重新赋值。

测试用例

@Test
public void cloneCopy() throws CloneNotSupportedException { Address address = new Address("杭州", "中国");
User user = new User("大山", address); // 调用clone()方法进行深拷贝
User copyUser = user.clone(); // 修改源对象的值
user.getAddress().setCity("深圳"); // 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }

方法三 Apache Commons Lang序列化

Java提供了序列化的能力,我们可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类(包括其成员变量)需要实现Serializable接口。Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。

重写代码

让我们修改一下User类,Address类,实现Serializable接口,使其支持序列化。

/**
* 地址
*/
public class Address implements Serializable { private String city;
private String country; // constructors, getters and setters }
/**
* 用户
*/
public class User implements Serializable { private String name;
private Address address; // constructors, getters and setters }

测试用例

@Test
public void serializableCopy() { Address address = new Address("杭州", "中国");
User user = new User("大山", address); // 使用Apache Commons Lang序列化进行深拷贝
User copyUser = (User) SerializationUtils.clone(user); // 修改源对象的值
user.getAddress().setCity("深圳"); // 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }

方法四 Gson序列化

Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝。

测试用例

@Test
public void gsonCopy() { Address address = new Address("杭州", "中国");
User user = new User("大山", address); // 使用Gson序列化进行深拷贝
Gson gson = new Gson();
User copyUser = gson.fromJson(gson.toJson(user), User.class); // 修改源对象的值
user.getAddress().setCity("深圳"); // 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }

方法五 Jackson序列化

Jackson与Gson相似,可以将对象序列化成JSON,明显不同的地方是拷贝的类(包括其成员变量)需要有默认的无参构造函数。

重写代码

让我们修改一下User类,Address类,实现默认的无参构造函数,使其支持Jackson。

/**
* 用户
*/
public class User { private String name;
private Address address; // constructors, getters and setters public User() {
} }
/**
* 地址
*/
public class Address { private String city;
private String country; // constructors, getters and setters public Address() {
} }

测试用例

@Test
public void jacksonCopy() throws IOException { Address address = new Address("杭州", "中国");
User user = new User("大山", address); // 使用Jackson序列化进行深拷贝
ObjectMapper objectMapper = new ObjectMapper();
User copyUser = objectMapper.readValue(objectMapper.writeValueAsString(user), User.class); // 修改源对象的值
user.getAddress().setCity("深圳"); // 检查两个对象的值不同
assertNotSame(user.getAddress().getCity(), copyUser.getAddress().getCity()); }

总结

说了这么多深拷贝的实现方法,哪一种方法才是最好的呢?最简单的判断就是根据拷贝的类(包括其成员变量)是否提供了深拷贝的构造函数、是否实现了Cloneable接口、是否实现了Serializable接口、是否实现了默认的无参构造函数来进行选择。如果需要详细的考虑,则可以参考下面的表格:

深拷贝方法 优点 缺点
构造函数 1. 底层实现简单 
2. 不需要引入第三方包 
3. 系统开销小 
4. 对拷贝类没有要求,不需要实现额外接口和方法
1. 可用性差,每次新增成员变量都需要新增新的拷贝构造函数
重载clone()方法 1. 底层实现较简单 
2. 不需要引入第三方包 
3. 系统开销小
1. 可用性较差,每次新增成员变量可能需要修改clone()方法 
2. 拷贝类(包括其成员变量)需要实现Cloneable接口
Apache Commons Lang序列化 1. 可用性强,新增成员变量不需要修改拷贝方法 1. 底层实现较复杂 
2. 需要引入Apache Commons Lang第三方JAR包 
3. 拷贝类(包括其成员变量)需要实现Serializable接口 
4. 序列化与反序列化存在一定的系统开销
Gson序列化 1. 可用性强,新增成员变量不需要修改拷贝方法 
2. 对拷贝类没有要求,不需要实现额外接口和方法
1. 底层实现复杂 
2. 需要引入Gson第三方JAR包  
3. 序列化与反序列化存在一定的系统开销
Jackson序列化 1. 可用性强,新增成员变量不需要修改拷贝方法 1. 底层实现复杂 
2. 需要引入Jackson第三方JAR包 
3. 拷贝类(包括其成员变量)需要实现默认的无参构造函数 
4. 序列化与反序列化存在一定的系统开销
 
https://wudashan.com/2018/10/14/Java-Deep-Copy/

Java如何对一个对象进行深拷贝的更多相关文章

  1. Java如何对一个对象进行深拷贝?

    在Java语言里,当我们需要拷贝一个对象时,有两种类型的拷贝:浅拷贝与深拷贝.浅拷贝只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化.而深拷贝则是拷贝了源对象的所有值,所以即 ...

  2. 【转】JAVA中的浅拷贝和深拷贝

    原文网址:http://blog.bd17kaka.net/blog/2013/06/25/java-deep-copy/ JAVA中的浅拷贝和深拷贝(shallow copy and deep co ...

  3. java 复制Map对象(深拷贝与浅拷贝)

      java 复制Map对象(深拷贝与浅拷贝) CreationTime--2018年6月4日10点00分 Author:Marydon 1.深拷贝与浅拷贝 浅拷贝:只复制对象的引用,两个引用仍然指向 ...

  4. jvm大局观之内存管理篇(二):当java中new一个对象,背后发生了什么

    https://zhuanlan.zhihu.com/p/257863129?utm_source=ZHShareTargetIDMore 番茄番茄我是西瓜 那是我日夜思念深深爱着的人啊~ 已关注   ...

  5. java中的浅拷贝与深拷贝

    浅拷贝: package test; class Student implements Cloneable { private int number; public int getNumber() { ...

  6. Java对象的浅拷贝和深拷贝&&String类型的赋值

    Java中的数据类型分为基本数据类型和引用数据类型.对于这两种数据类型,在进行赋值操作.方法传参或返回值时,会有值传递和引用(地址)传递的差别. 浅拷贝(Shallow Copy): ①对于数据类型是 ...

  7. Java原型模式之浅拷贝-深拷贝

    一.是什么? 浅拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量仅仅复制引用,不复制引用的对象 深拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量也进行引用对象的复制 内部机制: ...

  8. 深入理解Java中的Clone与深拷贝和浅拷贝

    1.Java对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象. ...

  9. java中的浅拷贝和深拷贝

    复制 将一个对象的引用复制给另一个对象,一共有三种方式.第一种方式是直接赋值,第二种方式是浅复制,第三种方式是深复制. 1.直接赋值 在Java中,A a1 = a2,这实际上复制的是引用,也就是说 ...

随机推荐

  1. 【LeetCode】988. Smallest String Starting From Leaf 解题报告(C++ & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS BFS 日期 题目地址:https://le ...

  2. 【LeetCode】976. Largest Perimeter Triangle 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 排序 日期 题目地址:https://leetcod ...

  3. 【LeetCode】501. Find Mode in Binary Search Tree 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  4. 【LeetCode】845. Longest Mountain in Array 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 双数组 参考资料 日期 题目地址:https://l ...

  5. GCD (hdu 5726)

    GCD Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submis ...

  6. [opencv]KAZE、AKAZE特征检测、匹配与对象查找

    AkAZE是KAZE的加速版 与SIFT,SUFR比较: 1.更加稳定 2.非线性尺度空间 3.AKAZE速度更加快 4.比较新的算法,只有Opencv新的版本才可以用 AKAZE局部匹配介绍 1.A ...

  7. 云南农职《JavaScript交互式网页设计》 综合机试试卷⑤——简单分类菜单

    一.语言和环境 实现语言:HTML,CSS,JavaScript,JQuery. 开发环境:HBuilder. 二.题目(100分): 1.使用Jquery和JavaScript实现二级分类菜单管理 ...

  8. 什么是NaN?它的类型是什么?如何可靠的测试一个值是否等于NaN?

    NaN属性表示"不是数字"的值.这个特殊值是由于一个操作数是非数字的(例如"abc"/4)或者因为操作的结果是非数字而无法执行的. 虽然看起来很简单,但是NaN ...

  9. 每天学一点——python用户的交互、格式化输出与基础运算符运用

    用户交互 input输入 input接收的数据都是字符串类型 如下图 output输出 还可以相加 换行符 想让他们隔行排列的话就可以这样(如图) 在想各行的开头前面加上\n即可 那若是想将两个输出的 ...

  10. Ubuntu18.04 + Windows10 双系统安装

    此处忽略Windows10安装!!! 准备 安装环境 OS:Windows10 CPU:Intel(R) Core(TM) i5-10600KF CPU @ 4.10GHz 4.10 GHz GPU: ...