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即可) 复杂(需递归或序列化)
数据隔离性 弱(修改影响原对象) 强(完全独立)

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

相关推荐
我叫黑大帅6 分钟前
如何排查 MySQL 慢查询
后端·sql·面试
techdashen7 分钟前
Rust项目公开征测:Cargo 构建目录新布局方案
开发语言·后端·rust
消失的旧时光-194316 分钟前
Spring Boot 实战(五):接口工程化升级(统一返回 + 异常处理 + 错误码体系 + 异常流转机制)
java·spring boot·后端·解耦
Rust研习社22 分钟前
Rust 智能指针 Cell 与 RefCell 的内部可变性
开发语言·后端·rust
夕颜1111 小时前
Skill 机器人 vs Hermes Agent:两种「AI 越用越聪明」的路径
后端
IT_陈寒2 小时前
SpringBoot自动配置把我都整不会了
前端·人工智能·后端
覆东流3 小时前
第1天:Python环境搭建 & 第一个程序
开发语言·后端·python
码事漫谈3 小时前
Token成本失控?两大开源方案如何重构AI编程成本结构
后端
橙露3 小时前
SpringBoot 全局异常处理:优雅封装统一返回格式
java·spring boot·后端
LiveWillChange3 小时前
第一阶段:基本功能实现
后端