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

120 阅读3分钟

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

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

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

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

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

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

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

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

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());
}

image.png

实现方式2:Maven中的BeanUtils类

image.png

那么什么时候使用:

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

四、深拷贝:

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

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

    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());
    }
}

image.png

那么什么时候使用:

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

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

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

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

总结关键知识点

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

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