Java对象拷贝的浅与深:如何选择?

news2024/9/21 18:32:53

在日常开发中,我们经常需要将一个对象的属性复制到另一个对象中。无论是使用第三方工具类还是自己手动实现,都会涉及到浅拷贝深拷贝的问题。本文将深入讨论浅拷贝的潜在风险,并给出几种实现深拷贝的方式,帮助大家避免潜在的坑。

一、什么是浅拷贝?

在Java中,浅拷贝只会复制对象的基本类型字段,而对引用类型字段只复制引用的内存地址,不会递归复制引用的对象。这意味着,多个对象共享同一个引用,修改其中一个对象的引用字段可能会影响其他对象。

示例:Hutool和Apache Common工具类的浅拷贝

在项目中我们常使用工具类如 HutoolBeanUtil.copyProperties() 或 Apache Commons 的 BeanUtils.copyProperties() 来进行对象的拷贝。这些工具类默认情况下都执行浅拷贝。

本篇以Hutool的举例,依赖如下

      <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
import cn.hutool.core.bean.BeanUtil;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class User {
    private Long userId;
    private String name;
    private String email;

    public static void main(String[] args) {
        User oldUser = new User(1L, "lps", "email");
        User newUser = new User();

        // 使用 Hutool 工具类拷贝属性
        BeanUtil.copyProperties(oldUser, newUser);

        // 修改原对象的 userId
        oldUser.setUserId(2L);

        // 输出新对象的属性
        System.out.println(newUser); // 结果:User(userId=1, name=lps, email=email)
    }
}

这个例子中的 Hutool 工具类对 oldUser 进行了浅拷贝。修改 oldUseruserId 并不会影响 newUser,因为 Long 是不可变类型。但如果 User 类中包含引用类型(例如 List、自定义对象),浅拷贝就会带来问题。

二、浅拷贝的潜在问题

浅拷贝最大的风险在于引用类型数据的共享。当你修改一个对象中的引用字段时,拷贝出来的对象也会随之改变。

示例:浅拷贝带来的问题
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class Address {
    private String city;
}

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class User {
    private Long userId;
    private String name;
    private String email;
    private Address address;

    public static void main(String[] args) {
        Address address = new Address("Beijing");
        User oldUser = new User(1L, "lps", "email", address);
        User newUser = new User();

        // 浅拷贝 oldUser 到 newUser
        BeanUtil.copyProperties(oldUser, newUser);

        // 修改 oldUser 的地址
        oldUser.getAddress().setCity("Shanghai");

        // 输出新对象的地址
        System.out.println(newUser.getAddress().getCity()); // 结果:"Shanghai"
    }
}

在这个例子中,修改了 oldUseraddress 对象,导致 newUseraddress 也被改变。这就是浅拷贝的典型问题。

三、深拷贝:如何避免共享引用的问题?

为了避免浅拷贝带来的问题,深拷贝通过递归地复制所有引用对象来确保两个对象完全独立。实现深拷贝有多种方式,下面介绍几种常见的做法。

1. 手动实现深拷贝

最常见的方法是手动在 clone() 方法中递归调用所有引用对象的 clone() 方法。

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class Address implements Cloneable {
    private String city;

    @Override
    protected Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); 
        }
    }
}

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class User implements Cloneable {
    private Long userId;
    private String name;
    private String email;
    private Address address;

    @Override
    protected User clone() {
        try {
            User cloned = (User) super.clone();
            cloned.setAddress(this.address.clone());  // 手动深拷贝
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

手动实现深拷贝虽然可以控制每个引用的拷贝逻辑,但对于复杂对象来说,编写和维护都比较繁琐。


2. 使用序列化实现深拷贝

序列化是另一种常见的深拷贝方法,它通过将对象序列化为字节流,再反序列化为新的对象来实现深拷贝。

public User deepCopy() {
    try {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(this);

        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        return (User) in.readObject();
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException("深拷贝失败", e);
    }
}

虽然序列化方法较为简单通用,但它要求所有参与拷贝的类都实现 Serializable 接口,并且序列化和反序列化的性能开销较大。

四、总结
  • 浅拷贝:通过工具类如 HutoolApache Commons 可以轻松实现属性拷贝,但要小心引用类型字段的共享问题。
  • 深拷贝:如果需要完整独立的对象,深拷贝是必要的。你可以选择手动实现 clone() 或使用序列化方式实现。

在选择合适的拷贝方式时,应根据对象的复杂度和性能需求作出决策。如果对象层级简单且性能要求较高,手动实现 clone() 是不错的选择;如果对象层级较复杂,可以考虑使用序列化来简化深拷贝的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2109148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot开发——整合Logbook进行HTTP API请求响应日志输出

文章目录 1. 简介依赖管理2. 实战案例2.1 基本用法2.2 结合Logback日志记录到文件2.3 自定义核心类Logbook2.4 自定义日志输出Sink2.5 与RestTemplate集成1. 简介 记录HTTP API请求响应日志对于监控、调试和性能优化至关重要。它帮助开发者追踪API的使用情况,包括请求来源、参…

基于ssm+vue+uniapp的“健康早知道”小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

好尴尬,借用的轮子在我这里还是没有运行起来

前期引用flask框架&#xff0c;由于版本不兼容&#xff0c;像是捅了flask-bug的窝。一开始是减低版本&#xff0c;然后一换卡一环&#xff0c;直接百度&#xff0c;试了很多办法都没有成功。 之后添加语句 下面展示一些 内联代码片。 # -*— coding:utf-8 -*— from datetime…

四战搜索,抖音难造“百度”

转载&#xff1a;新熵 原创 作者丨余寐 编辑丨九犁 抖音搜索野心暴露无遗&#xff01;接连4次发起猛攻&#xff0c;这是要颠覆搜索市场的节奏&#xff1f;还是因为流量触顶&#xff0c;急寻新入口&#xff1f; 执念太深&#xff01;抖音还是没放弃搜索&#xff0c;并发起一场…

人工智能在C/C++中的应用

随着技术的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经成为我们日常生活中不可或缺的一部分。从智能手机的语音助手到自动驾驶汽车&#xff0c;AI的应用无处不在。在众多编程语言中&#xff0c;C和C因其高性能和灵活性&#xff0c;成为实现复杂AI算法的理想选…

回调函数是什么,为什么要有回调函数?有什么优缺点?回调的本质是什么?

目录 前言 一、回调函数是什么&#xff1f; 二、为什么要有回调函数&#xff1f; 三、回调函数的优缺点 四、回调的本质是什么&#xff1f; 五、回调函数的实现方式 六、函数指针、Lambda 表达式、std::function&#xff1a; std::function这三者有什么不一样 1. 函数指…

仪表盘echarst

var bgColor #041F34,borderColor "#fff"let dataVal20 option {backgroundColor: bgColor,color: [borderColor],title: [{text: 处理率,x: center,top: 40%,textStyle: {color: #FFE600,fontSize: 56,fontWeight: 600,},},],series: [{type: pie,zlevel: 1,radi…

数据结构----高度为h的m叉树(记录一题)

&#xff08;1&#xff09;各层结点个数&#xff1a; 类比二叉树可得&#xff1a; 所以各层结点个数&#xff1a; &#xff08;2&#xff09;编号为i的结点的双亲结点(若存在)的编号是多少? 若存在表示&#xff1a;i>1(根节点没有双亲结点) 假设i结点有左兄弟和右兄弟&a…

javaweb项目1

1.配置servlet 注意&#xff1a;需要在web.xml进行操作。 2.执行原理 3.五个方法 1.init 在servlet创建的时候&#xff0c;执行&#xff0c;并且只执行一次。 init 方法可以用来执行 Servlet 的初始化逻辑&#xff0c;比如&#xff1a; 读取配置参数初始化数据库连接加载资…

深入理解Docker核心原理:全面解析Docker Client

随着云计算与容器技术的飞速发展&#xff0c;Docker已经成为软件开发、部署和运维中的重要工具之一。在Docker的架构中&#xff0c;Docker Client作为用户操作Docker系统的接口&#xff0c;起着至关重要的作用。本文将详细解析Docker Client的核心原理、工作机制、常用命令以及…

Ignis公链探索生态建设新范式:产业区块链与GameFi双轨驱动

Ignis公链凭借其独特的技术架构&#xff0c;选择了产业区块链与GameFi这两个赛道作为生态建设的双轮驱动&#xff0c;逐步形成了一个多元化的Web3生态系统。 一、产业区块链的革新&#xff1a;Vessel Chain的成功案例 在产业区块链领域&#xff0c;Ignis公链通过推出Vessel Ch…

JUC面试知识点手册

第一章&#xff1a;Java并发简介 1.1 什么是并发编程 并发编程是指在同一时间段内执行多个任务的编程方式。在单核处理器上&#xff0c;并发通过时间分片来实现&#xff0c;即在同一时间只有一个任务在执行&#xff0c;其他任务被暂停等待。在多核处理器上&#xff0c;并发可…

C语言函数原理——深入底层机制

概述 在C语言中&#xff0c;函数是封装代码复用和模块化的关键机制。为了更好地理解函数如何工作&#xff0c;我们需要深入了解函数的定义、调用机制、参数传递方式、以及函数与内存管理的关系。本文将探讨函数的底层实现、调用过程、以及它们如何影响程序的行为。 函数定义 …

优盘数据丢失怎么办?本文带你一览优盘数据恢复

u盘格式化后数据能恢复吗&#xff1f;答案是肯定的。现在数据通过一些优盘或者移动硬盘之类介质进行传输已经一种很常见的文件传输方式了。但是我们偶尔就因为一些意外导致数据的丢失&#xff0c;这次我就来分享一些可以找回丢失数据的工具。 1.福昕数据恢复 链接直达&#…

cesium 使用异步函数 getHeightAtPoint,获取指定经纬度点的地形高度。

这个函数使用 CesiumJS 库的 sampleTerrain 方法来获取地形数据。下面是代码的详细解释&#xff1a; async getHeightAtPoint(LngLat) {// 将经纬度转为 Cartographic 对象let cartographics [Cesium.Cartographic.fromDegrees(LngLat[0], LngLat[1])];// console.log("…

数组与贪心算法——605、121、122、561、455、575(5简1中)

605. 种花问题&#xff08;简单&#xff09; 假设有一个很长的花坛&#xff0c;一部分地块种植了花&#xff0c;另一部分却没有。可是&#xff0c;花不能种植在相邻的地块上&#xff0c;它们会争夺水源&#xff0c;两者都会死去。 给你一个整数数组 flowerbed 表示花坛&#xf…

千行百业用AI大模型,为什么火山引擎是聚处?

“角儿是座儿叫出来的”&#xff0c;这句话不仅适合相声艺术&#xff0c;也很符合AI大模型商业化的现状。 今年以来&#xff0c;“大模型落地”成为AI和云产业的高频词。避免“叫好不叫座”&#xff0c;让AI大模型更快地融入行业场景之中&#xff0c;被各行各业真正用起来&…

CSS之我不会

一、选择器 作用&#xff1a;选择页面上的某一个后者某一类元素 基本选择器 1.标签选择器 格式&#xff1a;标签{} <h1>666</h1><style>h1{css语法} </style>2.类选择器 格式&#xff1a;.类名{} <h1 class"name">666</h1>…

uniapp组件知识记录

style标签的lang <template><view class"content"><h1 class"test"><span class"test1">我</span></h1>是谁</view> </template><style lang"scss">.content {// content中允…

基于Java+SpringBoot+Vue+MySQL的高校物品捐赠管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于SpringBootVue的高校物品捐赠管理系统【附源码文档】、…