作者丨吴大山

wudashan.com/2018/10/14/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@Overridepublic Address clone() throws CloneNotSupportedException {return (Address) super.clone();}}
/*** 用户*/
public class User implements Cloneable {private String name;private Address address;// constructors, getters and setters@Overridepublic 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 setterspublic User() {}}
/*** 地址*/
public class Address {private String city;private String country;// constructors, getters and setterspublic 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接口、是否实现了默认的无参构造函数来进行选择。如果需要详细的考虑,则可以参考下面的表格:

什么是 Java 对象深拷贝?面试必问!相关推荐

  1. 看完946页“JAVA高级架构面试必问”,金九银十社招全拿下

    前言 我本科毕业后在老东家干了两年多,老东家算是一家"小公司"(毕竟这年头没有 BAT 或 TMD 的 title 都不好意思报出身),毕业这两年多我也没有在大厂待过,因此找坑的时 ...

  2. Java年薪30W+面试必问知识之《2020年面试宝典总纲》

    前言 一份月薪30K的java开发岗位工作要求是怎样的呢?面试都会问到哪些呢? 任职要求: 1.计算机或相关专业本科(或以上)学历,具备3年以上Java服务端开发经验,熟悉常用的Java开源框架,如熟 ...

  3. 『图解Java并发』面试必问的CAS原理你会了吗?

    在并发编程中我们都知道i++操作是非线程安全的,这是因为 i++操作不是原子操作. 如何保证原子性呢?常用的方法就是加锁.在Java语言中可以使用 Synchronized和CAS实现加锁效果. Sy ...

  4. java死锁2_Java面试必问-死锁终极篇(2)

    当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) ...

  5. 面试必问一:Java 中 == 和 equals 的区别你知道吗

    面试必问一:Java 中 == 和 equals 的区别你知道吗 前言 关于这个问题,一般初中级面试中都会遇到,还记得我当初实习找工作的时候也遇到了这个问题,现在都还记得自己是怎么回答的:== 是基本 ...

  6. java中的静态、动态代理模式以及Spring中的CgLib动态代理解读(面试必问)

    java中的静态.动态代理模式以及Spring中的CgLib动态代理解读(面试必问) 静态代理 动态代理 CgLib动态代理     基础知: 反射知识 代理(Proxy)是一种设计模式,提供了对目标 ...

  7. Java面试必问!javasocket服务端持久化

    前言 最近刷到了一句耐人寻味的话,"解决雪崩问题的最好办法是不发生雪崩". 不论是在硅谷互联网公司里还是在国内的互联网平台上,曾多次遇到过海量规模的交易瞬间吞噬平台的悲惨故事. 核 ...

  8. Java 面试必问题目,Java 后端校招面试题

    字节跳动一面: 自我介绍,主要讲讲做了什么和擅长什么 看你项目做 Spring 比较多, 问一下 Spring 相关的东西, IoC 是什么概念? Bean 的默认作用范围是什么?其他的作用范围? 索 ...

  9. 线程同步有几种方法_架构师面试必问的多线程状态切换及常用方法

    架构师面试必问的多线程状态切换及常用方法 一.问题背景 Java架构师面试中,多线程状态切换及常用方法几乎是必问的,要掌握创建多线程的方式和方法. 二.创建多线程的几种方式 2.1方式一继承Threa ...

  10. 互联网公司面试必问的mysql题目(上)

    又到了招聘的旺季,被要求准备些社招.校招的题库. 介绍:MySQL是一个关系型数据库管理系统,目前属于 Oracle 旗下产品.虽然单机性能比不上oracle,但免费开源,单机成本低且借助于分布式集群 ...

最新文章

  1. 下拉菜单连动效果的一种用法
  2. c语言原始,[蓝桥杯][历届试题]回文数字 最原始的方法(C语言代码)
  3. mybatis中条件表达式if的test为字符串时值比较
  4. 12c闪回 oracle_Oracle12.1闪回功能
  5. oracle执行runstats,oracle runstats工具
  6. java8 按条件过滤集合
  7. 解决实例化Servlet类[com.mu.servlet.HelloServlet]异常
  8. com口驱动_四足机器人FOC驱动器篇1:Odrive Moco接口板套件介绍
  9. Java性能优化的七个方向
  10. java流程图表示输入 输出,用流程图描述算法
  11. python软件怎么打开画图_Python实现画图软件功能
  12. 97. ExtJS之EditorGridPanel afteredit属性
  13. knockoutjs的某些坑总结
  14. 对接金蝶云星空审批流
  15. php中求10递归算法,PHP递归算法的应用(含示例)
  16. sublime text3 boxy主题 (本地 压缩包 安装)
  17. 计算机教室消防说明,6.7 消防专用电话的设置
  18. 七牛云持久化上传图片及生成缩略图,多文件打包下载
  19. 关于ORA-12505, TNS:listener does not currently know of SID given in connect descript的一个解决思路
  20. json和pickle的数据序列化

热门文章

  1. 程序员如何明智地提出好的问题
  2. Java并发基础01. 传统线程技术中创建线程的两种方式
  3. [Java并发编程实战] 共享对象之可见性
  4. MySQL row_format引发的案例一则
  5. C#开发微信门户及应用(44)--微信H5页面开发的经验总结
  6. vmtouch--the Virtual Memory Toucher
  7. FTP服务器的安装和配置
  8. 1.A+B Problem
  9. InstallShield 2011新功能试用(3)- Script Editor Intellisense
  10. ubuntu下安装极点五笔