Java对象克隆的深与浅:彻底搞懂拷贝陷阱

作为一个老牛马程序员,在编写代码的时候肯定遇到过对象需要克隆的问题,但是有时候比如说修改了克隆的副本以后,把原始数据也同步的给改了,这种情况还是挺烦人的,也可能不是我们要的效果。

为什么呢?因为克隆的时候它是分深拷贝和浅拷贝两种情况的,接下来我们就这两种情况进行一个好好的分析!

一、技术核心:堆内存中的对象真相

Java对象在内存中分两部分存储:

java 复制代码
Person person1 = new Person("张三", new Address("北京"));
  • person1 变量本身(对象引用)存在栈内存
  • 实际包含数据(name="张三", address对象)存在堆内存

二、浅拷贝:只复制第一层

浅拷贝只复制原始对象本身,不复制其引用的其他对象。新老对象共享内部引用对象。

实现方式1:默认的clone()方法

java 复制代码
HardwareParameters implements Cloneable {
    String name;
    Address address; // 引用类型

  //重写clone()方法,因为它是的类型是protected的
@Override
public HardwareParameters clone() throws CloneNotSupportedException {
    return (HardwareParameters) super.clone();
    }
}


public static void main(String[] args) throws CloneNotSupportedException {
    HardwareParameters hardwareParameters1 = new HardwareParameters("柜体尺寸", new CaseList("这是第一个呀"));
    //调用重写的clone方法
    HardwareParameters hardwareParameters2 = (HardwareParameters) hardwareParameters1.clone();
    //修改拷贝后的参数
    hardwareParameters2.getCaseList().setTitle("这是第二个呀");

    System.out.println("hardwareParameters2的值:"+hardwareParameters2.getCaseList().getTitle());

    System.out.println("hardwareParameters1的值: "+hardwareParameters1.getCaseList().getTitle());
}

实现方式2:Maven中的BeanUtils类

那么什么时候使用:

  • 只包含基本类型的DTO对象传输
  • 临时对象快照(确保不修改原始对象的基础属性)
  • 配合不可变对象(如String)使用

四、深拷贝:

深拷贝会递归复制所有层级的对象,生成一个完全独立的对象网络。

实现方式:特别简单直接使用JSON序列化

java 复制代码
    public static void main(String[] args)  {
        Gson gson = new Gson();
        HardwareParameters hardwareParameters1 = new HardwareParameters("柜体尺寸", new CaseList("这是第一个呀"));
//        HardwareParameters hardwareParameters2 = new HardwareParameters();

        HardwareParameters hardwareParameters2 = gson.fromJson(gson.toJson(hardwareParameters1), HardwareParameters.class);

//        BeanUtils.copyProperties(hardwareParameters1, hardwareParameters2);
        //修改拷贝后的参数
        hardwareParameters2.getCaseList().setTitle("这是第二个呀");

        System.out.println("hardwareParameters2的值:"+hardwareParameters2.getCaseList().getTitle());

        System.out.println("hardwareParameters1的值: "+hardwareParameters1.getCaseList().getTitle());
    }
}

那么什么时候使用:

  • 需要修改副本且不影响原始对象的场景(如缓存数据更新)
  • 复杂对象图的版本快照
  • 多线程环境下共享数据的安全隔离
  • 游戏开发中的状态保存/恢复

五、关键决策:如何选择拷贝方式?

根据我的项目经验,遵循以下原则能避开90%的坑:

  1. 对象结构简单 → 优先考虑浅拷贝
  2. 包含嵌套可变对象 → 必须深拷贝
  3. 性能敏感 → 浅拷贝速度更快(深拷贝递归消耗大)
  4. 网络传输或持久化 → 序列化实现深拷贝最可靠

总结关键知识点

特性 浅拷贝 深拷贝
复制深度 仅第一层对象 所有层级对象
引用对象处理 共享引用 创建新对象
实现复杂度 简单(默认clone即可) 复杂(需递归或序列化)
数据隔离性 弱(修改影响原对象) 强(完全独立)

下次遇到对象复制需求时,不妨先问自己:此处需要的是复印件,还是完全独立的副本?答案自然就很清晰了。

相关推荐
Victor3564 分钟前
Redis(118)Redis的缓存机制如何实现?
后端
Yeats_Liao5 小时前
时序数据库系列(五):InfluxDB聚合函数与数据分析
java·后端·数据分析·时序数据库
你的人类朋友8 小时前
✍️记录自己的git分支管理实践
前端·git·后端
像风一样自由20208 小时前
Go语言入门指南-从零开始的奇妙之旅
开发语言·后端·golang
合作小小程序员小小店8 小时前
web网页开发,在线考勤管理系统,基于Idea,html,css,vue,java,springboot,mysql
java·前端·vue.js·后端·intellij-idea·springboot
间彧10 小时前
SpringBoot + MyBatis-Plus + Dynamic-Datasource 读写分离完整指南
数据库·后端
间彧10 小时前
数据库读写分离下如何解决主从同步延迟问题
后端
码事漫谈10 小时前
C++中的线程同步机制浅析
后端
间彧10 小时前
在高并发场景下,动态数据源切换与Seata全局事务锁管理如何协同避免性能瓶颈?
后端
码事漫谈10 小时前
CI/CD集成工程师前景分析:与开发岗位的全面对比
后端