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

2023-10-30

深拷贝实现代码: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. 序列化与反序列化存在一定的系统开销

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • 第3篇:JVM中内存分配及回收策略

    文章目录 一 分配原理 二 原理图 小结 一 分配原理 当执行创建对象操作时 首先进行逃逸分析 其实就是该对象是否会被外部方法所引用 就是作用域 若不会则进行标量替换 就是对象中成员变量是基本数据类型的在栈帧 寄存器中进行创建 可以分担堆内
  • vue 递归组件

    递归组件 递归组件就是在模板中引用自身的组件 我们有时希望在一个组件内部渲染该组件本身 例如渲染树形结构时 需要在树根渲染子树 而子树与树根的结构是一样的 因此存在递归 一个简单的递归组件的例子如Tree vue Tree vue
  • 计算机温度压力测试,鲁大师温度压力测试怎么看结果 鲁大师的温度压力测试教程...

    很多朋友都在网上问鲁大师的温度压力测试怎么看结果 结果到底在哪里 其实小编也想知道 下面就是小编收集来的各路大神的说法 大家可以看看对比一下 说法一 鲁大师的温度压力测试不需要你开任何应用程序的 你只需要点击鲁大师 温度检测 里的 进行温度
  • Python3爬虫学习——urlib库笔记

    Python3爬虫学习 urllib库 前言 本笔记仅个人认知和见解 水平有限 还请见谅 内容大多来自Python文档和学习材料 作相应的扩充或压缩后的笔记 没有很多实例 大多是理论知识 文章目录 Python3爬虫学习 urllib库 前
  • wordpress付费阅读_免费和付费WordPress托管之间的7个区别

    wordpress付费阅读 If you ve been looking around for WordPress hosting you might have come across a number of companies offer
  • python lambda的用法

    欢迎转载 转载请注明原文地址 http blog csdn net majianfei1023 article details 45269343 lambda函数也叫匿名函数 函数没有具体的名称 先来看一个最简单例子 python view
  • IDC:云效产品能力No.1,领跑中国DevOps市场

    近日 全球领先的专业市场调查机构国际数据公司 IDC 发布了 IDC MarketScape 中国 DevOps 平台市场厂商评估 2022 报告 此报告中对中国主流 DevOps 云厂商从战略 Strategies 能力 Capabili
  • 详解rem布局-利用rem布局实现移动端高清显示

    目录 目录 一 初探rem布局 1 1 rem是什么 1 2 rem实现新闻字体 小中大 设置 二 利用rem布局实现移动端高清显示 一 初探rem布局 1 1 rem是什么 rem是CSS3新增的一个相对单位 root em 根em 这个
  • Android图片加载神器之Fresco,基于各种使用场景的讲解

    Fresco是Facebook开源Android平台上一个强大的图片加载库 也是迄今为止Android平台上最强大的图片加载库 优点 相对于其他开源的第三方图片加载库 Fresco拥有更好的内存管理和强大的功能 基本上能满足所有的日常使用场
  • TurboPower Async Professional 在Delphi2010及Delphi7中的安装

    这里我们介绍一下TurboPower Async Professional 串口控件的安装方法 Delphi 2010 1 下载http sourceforge net projects tpapro 2 解压 在Delphi2010下找到
  • python正则表达式爬取【豆瓣电影top250】(新手向)

    最近在学崔大的 网络爬虫开发与实战 学到正则表达式那块儿 便迎来了自己的第一个实战项目 话不多说 正式进入正文 本次爬虫工具使用的是pycharm 已经提前安装好了所有包 安装方式 file Settings Project Interpr
  • 双亲委派机制及其部分源码分析

    双亲委派机制 双亲委派机制 我理解的 双亲委派机制 简单来讲就是 类加载器加载类的时候是 自顶向下 的过程来加载 详情如下 在某个加载器进行类加载是 会逐级向上找到他最终的父类 BootstrapClassLoader 先进行加载 逐级向下
  • List元素移除-迭代器删除

    Exception in thread main java util ConcurrentModificationException异常解决方案 使用迭代器方式删除List元素内容 当直接用List的remove移除元素时 会报如上异常 比
  • Hinton关于RBM的代码注解之(三)mnistclassify.m

    mnistclssify m clear all close all maxepoch 50 最大迭代次数 numhid 500 numpen 500 numpen2 2000 对应的1 2 3层隐含层单元的个数 fprintf 1 Con
  • C++ 的封装、继承、多态

    面向对象的三个基本特征 面向对象的三个基本特征是 封装 继承 多态 封装可以隐藏实现细节 使得代码模块化 继承可以扩展已存在的代码模块 类 多态则是为了实现另一个目的 接口重用 它们的目的都是为了 代码重用 封装 目的 隐藏实现细节 使得代
  • Python 控制 Raspberry Pi 云台多舵机

    多舵机控制 使用 Python 和云台机制构造进行 Raspberry Pi 相机定位 所需材料 在本教程中 我们将探索如何在 Raspberry Pi 上使用 Python 控制多个舵机 我们的目标是使用云台机制来定位相机 PiCam 如
  • 【测试开发】基于 MeterSphere 的接口测试流程

    基于 MeterSphere 的接口测试流程 MeterSphere 接口测试模块提供了 接口定义 接口自动化 等接口测试相关功能 用户可以使用树状多级模块来分级分组管理项目下的接口列表 创建执行接口用例测试接口 组合编排多个接口用例进行场
  • 电子产品推荐系统的设计与实现

    其他项目 点击作者主页 目录 1 系统简介 2 系统相关技术 2 1 JSP技术 2 2 B S架构 2 3 MySQL数据库技术 2 4 SSM 3 需求分析 3 1 系统功能需求分析 3 2 系统非功能需求分析 4 系统设计 4 1 系
  • websocket协议

    WebSocket是一种在Web应用程序中实现实时双向通信的协议 一种在单个TCP连接上进行全双工通信的协议 它使得客户端和服务器之间的数据交换变得更加简单 允许服务端主动向客户端推送数据 WebSocket 与 HTTP 2 一样 其实都
  • Java如何对一个对象进行深拷贝?

    深拷贝实现代码 https github com wudashan java deep copy 介绍 在Java语言里 当我们需要拷贝一个对象时 有两种类型的拷贝 浅拷贝与深拷贝 浅拷贝只是拷贝了源对象的地址 所以源对象的值发生变化时 拷