当前位置: 首页 > news >正文

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等不可变类,因为其值不可修改,即使是浅拷贝,也不会出现数据联动问题。

六、 总结

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

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

http://www.cnnetsun.cn/news/3120.html

相关文章:

  • 干货分享|N8N一键将视频转为爆款文章!
  • Day34 PythonStudy
  • 【卫星】基于GNSS单点定位解算系统,通过卫星伪距观测数据计算接收机的地理位置(纬度、经度、高度)和速度,涉及坐标系转换、卫星位置计算、萨格纳克效应修正、最小二乘定位解算等关键步骤附matlab代码
  • 【LSTM回归预测】基于黑翅鸢优化算法BKA-LSTM-Multihead-Attention多变量时序预测 matlab代码
  • 【MTSP问题】基于鲸鱼迁徙算法WMA求解单仓库多旅行商问题附Matlab代码
  • 【优化排程】基于全局邻域和爬山法优化模糊灵活工作间排程问题附Matlab代码
  • 【优化求解】基于粒子群算法PSO优化风能到氢气系统附Matlab代码
  • Python MySQL从零上手:30分钟搞定环境搭建与驱动选型
  • 【路径规划】基于RRT算法的路径规划算法实现多无人机的协同运动控制附matlab代码
  • 小巧工具,吾爱出品
  • Logo设计神器,绝了
  • 【LibreCAD】LibreCAD 撤销功能相关类(LC_UndoableRelZero)
  • 智谱开源 GLM-ASR 系列语音识别模型;Pebble 发布智能指环 Index 01:本地语音转录与指令执行丨日报
  • Vibe Coding 你应该更激进:用最 SOTA 的模型,赚最高的时薪|编码人声
  • 2025年的大模型使用——各有所长,各有不同
  • 智能汽车解决方案构成,形成从环境感知到车辆控制的完整生态
  • NPU编程范式的革命 - 基于MlaProlog案例的“软件定义计算流“实践
  • 超越MlaProlog:构建自定义CV融合算子的通用设计模式库
  • 2026年远程控制软件选择指南!内含十款主流应用深度横评
  • Mac桌面远比你以为的更强大,ToDesk加持轻松控安卓!
  • 它为工厂省下的每一颗螺丝钉里,藏着多大未来?
  • 【Python】水位测量与水体识别技术实现,基于Cascade R-CNN模型详解_1
  • 基于YOLO11-C3k2-MambaOut-FDConv的汗宫建筑检测与识别技术
  • 【焊接检测技术】基于YOLO11-SEG-REPVGGOREPA模型的焊接缺陷检测与分类系统详解
  • 【珍藏】RAG实战指南:解决大模型应用痛点,附学习路径
  • 【深度收藏】转岗AI产品经理全攻略:市场需求分析与能力提升路径
  • 从零开始学大模型:收藏这份完整学习路径,助你成为AI应用开发工程师
  • 收藏必备!AI产品经理转型指南:10年总监亲授核心能力清单,小白也能快速上手
  • GLM-4.6V:从视觉理解到行动执行
  • 企业不良记录清除方法