news 2026/1/14 12:42:47

Java 深拷贝与浅拷贝,一篇吃透所有坑!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 深拷贝与浅拷贝,一篇吃透所有坑!

文章目录

  • 前言
  • 一、 前置知识:Java 的数据类型与对象存储
  • 二、 浅拷贝(Shallow Copy):只拷贝 “表面”
    • 1. 浅拷贝的定义
    • 2. 浅拷贝的实现方式
    • 3. 浅拷贝的核心特点
  • 三、 深拷贝(Deep Copy):拷贝 “完全独立” 的对象
    • 1. 深拷贝的定义
    • 2. 深拷贝的两种实现方式
    • 3. 深拷贝的核心特点
  • 四、 浅拷贝 vs 深拷贝,核心区别对比
  • 五、 开发中的避坑指南
  • 六、 总结

前言

大家好,我是程序员梁白开,今天我们聊一聊Java 深拷贝与浅拷贝。

在 Java 开发中,对象复制是高频操作。你是否遇到过 “修改新对象,原对象也跟着变” 的诡异情况?是否在clone()方法上踩过无数坑?这些问题的根源,都在于没分清浅拷贝(Shallow Copy)和深拷贝(Deep Copy)的本质区别


一、 前置知识:Java 的数据类型与对象存储

要搞懂深浅拷贝,必须先明白 Java 中数据的存储逻辑,这是一切的基础!

Java 中的数据类型分为两类:

  1. 基本数据类型:byte、short、int、long、float、double、char、boolean
    • 存储位置:直接存储在栈内存中,变量保存的是值本身。
    • 赋值逻辑:赋值时直接拷贝值,新旧变量互不影响。
  2. 引用数据类型:类、接口、数组
    • 存储位置:对象本身存在堆内存,变量保存的是堆内存的地址引用。
    • 赋值逻辑:赋值时拷贝的是地址引用,新旧变量指向同一个堆内存对象。

举个简单的赋值例子:

// 基本数据类型赋值inta=10;intb=a;b=20;System.out.println(a);// 输出10,a不受b影响// 引用数据类型赋值Useruser1=newUser("张三",20);Useruser2=user1;// 拷贝的是引用地址user2.setName("李四");System.out.println(user1.getName());// 输出李四,原对象被修改!

二、 浅拷贝(Shallow Copy):只拷贝 “表面”

1. 浅拷贝的定义

浅拷贝是指:创建一个新对象,新对象的基本数据类型成员变量会拷贝原对象的值;但引用数据类型成员变量,拷贝的是原对象的地址引用。
简单说:浅拷贝只拷贝 “第一层”,引用类型成员变量还是共用同一个堆对象。

2. 浅拷贝的实现方式

Java 中实现浅拷贝有两种常见方式:

  • 方式 1:手动 new 对象,逐个赋值
  • 方式 2:实现Cloneable接口,重写clone()方法(默认浅拷贝)、

方式 1:手动浅拷贝
手动创建新对象,将原对象的成员变量逐个赋值,引用类型变量直接赋值地址。

// 用户类classUser{privateStringname;// 引用类型privateintage;// 基本类型// 构造器、getter、setter省略}// 手动浅拷贝publicclassShallowCopyDemo{publicstaticvoidmain(String[]args){Useruser1=newUser("张三",20);// 手动浅拷贝Useruser2=newUser();user2.setName(user1.getName());user2.setAge(user1.getAge());// 修改基本类型user2.setAge(25);System.out.println(user1.getAge());// 输出20,不受影响// 修改引用类型(String是不可变类,这里换个例子更直观)// 我们换一个自定义引用类型成员变量演示,比如给User加一个Address属性}}

方式 2:实现Cloneable接口(推荐)

Java 中Object类提供了clone()方法,但它是protected修饰的,且默认是浅拷贝。要使用该方法,需要:

  1. 实现Cloneable标记接口(没有任何方法,仅作标记)
  2. 重写clone()方法,修改访问权限为public

我们给User类增加一个引用类型成员变量Address,更直观地看浅拷贝的问题:

// 地址类(引用类型)classAddress{privateStringcity;// 构造器、getter、setter省略publicAddress(Stringcity){this.city=city;}}// 用户类实现Cloneable接口classUserimplementsCloneable{privateStringname;// 引用类型privateintage;// 基本类型privateAddressaddr;// 自定义引用类型// 构造器、getter、setter省略publicUser(Stringname,intage,Addressaddr){this.name=name;this.age=age;this.addr=addr;}// 重写clone方法,默认浅拷贝@OverridepublicObjectclone()throwsCloneNotSupportedException{returnsuper.clone();}}// 测试浅拷贝publicclassShallowCopyTest{publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Addressaddr=newAddress("北京");Useruser1=newUser("张三",20,addr);// 浅拷贝Useruser2=(User)user1.clone();// 1. 修改基本类型成员变量user2.setAge(25);System.out.println(user1.getAge());// 输出20,不受影响// 2. 修改引用类型成员变量(核心坑点!)user2.getAddr().setCity("上海");System.out.println(user1.getAddr().getCity());// 输出上海!原对象被修改}}

3. 浅拷贝的核心特点

  • 优点:实现简单,拷贝效率高。
  • 缺点:引用类型成员变量共享堆内存,修改新对象的引用成员,会影响原对象,存在数据安全风险。

三、 深拷贝(Deep Copy):拷贝 “完全独立” 的对象

1. 深拷贝的定义

深拷贝是指:创建一个新对象,不仅拷贝原对象的基本数据类型成员变量值,还会为引用数据类型成员变量重新创建一个独立的新对象。

简单说:深拷贝会拷贝 “所有层级”,新对象和原对象完全独立,修改任何一方都不会影响另一方。

2. 深拷贝的两种实现方式

深拷贝的实现比浅拷贝复杂,常见的有两种方式:

  • 方式 1:嵌套重写clone()方法(适用于自定义类较少的场景)
  • 方式 2:序列化与反序列化(通用推荐,适用于复杂对象)

方式 1:嵌套重写clone()方法

思路:不仅要让外层类实现Cloneable,其所有引用类型成员变量对应的类,也需要实现Cloneable并重写clone()方法,然后在外层类的clone()方法中手动拷贝引用成员。

// 地址类实现Cloneable接口classAddressimplementsCloneable{privateStringcity;publicAddress(Stringcity){this.city=city;}// getter、setter省略// 地址类重写clone方法@OverridepublicObjectclone()throwsCloneNotSupportedException{returnsuper.clone();}}// 用户类classUserimplementsCloneable{privateStringname;privateintage;privateAddressaddr;// 构造器、getter、setter省略// 重写clone方法,实现深拷贝@OverridepublicObjectclone()throwsCloneNotSupportedException{// 1. 先拷贝外层对象(浅拷贝)Useruser=(User)super.clone();// 2. 再手动拷贝引用类型成员变量(关键步骤!)user.addr=(Address)this.addr.clone();returnuser;}}// 测试深拷贝publicclassDeepCopyTest{publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Addressaddr=newAddress("北京");Useruser1=newUser("张三",20,addr);Useruser2=(User)user1.clone();// 修改新对象的引用成员变量user2.getAddr().setCity("上海");// 原对象不受影响!System.out.println(user1.getAddr().getCity());// 输出北京}}

方式 2:序列化与反序列化(推荐)

思路:将对象序列化为字节流,再从字节流反序列化为新对象,这个过程会自动创建一个完全独立的新对象。

注意:所有涉及的类都需要实现Serializable标记接口,否则会抛出序列化异常。

importjava.io.*;// 地址类实现SerializableclassAddressimplementsSerializable{privatestaticfinallongserialVersionUID=1L;// 序列化版本号privateStringcity;// 构造器、getter、setter省略}// 用户类实现SerializableclassUserimplementsSerializable{privatestaticfinallongserialVersionUID=1L;privateStringname;privateintage;privateAddressaddr;// 构造器、getter、setter省略// 深拷贝工具方法publicUserdeepCopy()throwsIOException,ClassNotFoundException{// 1. 序列化:将对象写入字节流ByteArrayOutputStreambos=newByteArrayOutputStream();ObjectOutputStreamoos=newObjectOutputStream(bos);oos.writeObject(this);// 2. 反序列化:从字节流读取新对象ByteArrayInputStreambis=newByteArrayInputStream(bos.toByteArray());ObjectInputStreamois=newObjectInputStream(bis);return(User)ois.readObject();}}// 测试序列化深拷贝publicclassDeepCopyBySerialize{publicstaticvoidmain(String[]args)throwsException{Addressaddr=newAddress("北京");Useruser1=newUser("张三",20,addr);Useruser2=user1.deepCopy();user2.getAddr().setCity("上海");System.out.println(user1.getAddr().getCity());// 输出北京,完全独立}}

3. 深拷贝的核心特点

  • 优点:新对象与原对象完全隔离,数据安全,不存在相互影响的问题。
  • 缺点:实现相对复杂,序列化方式会有一定的性能开销。

四、 浅拷贝 vs 深拷贝,核心区别对比

为了方便大家记忆,我们用一张表格总结两者的核心区别:

特性浅拷贝深拷贝
拷贝层级仅拷贝第一层(基本类型值 + 引用地址)拷贝所有层级(基本类型值 + 引用对象新实例)
引用成员变量与原对象共享同一个引用对象重新创建独立的引用对象
修改影响修改新对象的引用成员,会影响原对象修改新对象,完全不影响原对象
实现难度简单,手动赋值或重写 clone () 即可较复杂,嵌套 clone 或序列化
性能效率高,开销小效率较低,序列化有额外开销
适用场景对象无引用类型成员,或无需独立引用成员对象包含多层引用类型,需要完全独立的拷贝

五、 开发中的避坑指南

  1. 不要滥用clone()方法:默认是浅拷贝,一定要检查引用成员变量是否会导致数据安全问题。
  2. 序列化深拷贝注意事项:
    • 所有相关类必须实现Serializable接口。
    • 静态变量不会被序列化,因为静态变量属于类,不属于对象。
    • transient修饰的变量不会被序列化,可用于排除不需要拷贝的敏感字段。
  3. 不可变类的特殊情况:比如String、Integer等不可变类,因为其值不可修改,即使是浅拷贝,也不会出现数据联动问题。

六、 总结

  • 直接赋值≠拷贝:引用类型赋值只是传递地址,新旧变量指向同一对象。
  • 浅拷贝:只拷贝第一层,引用成员共享,适合简单对象。
  • 深拷贝:拷贝所有层级,对象完全独立,适合复杂嵌套对象。

选择哪种拷贝方式,核心看业务需求:如果只是临时使用且不需要修改引用成员,浅拷贝足够;如果需要长期存储或修改数据,一定要用深拷贝保证数据安全!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!