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

Java:对象的浅拷贝与深拷贝

目录

一、概念

二、实现方式

2.1 浅拷贝(不推荐)

2.2 深拷贝

2.2.1 方法一:重写 clone() 方法并递归克隆(常用)

2.2.2 方法二:通过序列化实现(更强大,但更重)

2.2.3 使用拷贝构造方法或拷贝工厂(推荐)


一、概念

浅拷贝 (Shallow Copy):只复制对象本身以及对象中的基本数据类型字段的值,而对于对象中的引用类型字段,则只复制其内存地址(即引用),而不复制引用的对象本身。

影响:原始对象和拷贝对象中的引用字段指向的是同一个堆内存中的子对象。修改其中一个对象的引用字段的内容,另一个对象的对应字段也会“看到”这个变化。

深拷贝 (Deep Copy):仅复制对象本身和基本数据类型字段的值,还会递归地复制所有引用类型字段所指向的对象,直到所有可达的对象都被复制。

影响:原始对象和拷贝对象完全独立,它们所有的引用字段都指向不同的对象。修改其中一个对象的任何内容,都不会影响另一个对象。

二、实现方式

2.1 浅拷贝(不推荐)

实现方式:依靠Object 类的 clone() 方法。

实现条件:

  • 被拷贝的类实现 Cloneable 接口(这是一个标记接口,没有方法)。

  • 被拷贝的类重写 Object 的 clone() 方法,并在其中调用 super.clone()

public class Person implements Cloneable {private String name;        // String (不可变对象,可视为基本类型)private int age;            // 基本类型private Address address;    // 引用类型// 构造方法、getters、setters 省略...@Overridepublic Object clone() throws CloneNotSupportedException {// 直接调用Object的clone()方法,完成基本数据类型和引用地址的复制return super.clone();}
}public class Address {private String city;private String street;// 构造方法、getters、setters...
}public class TestShallowCopy {public static void main(String[] args) throws CloneNotSupportedException {Address addr = new Address("北京", "长安街");Person original = new Person("张三", 25, addr);// 进行浅拷贝Person copied = (Person) original.clone();System.out.println(original == copied);       // false,是两个不同的对象System.out.println(original.getName() == copied.getName()); // true,String池或同一对象(可能)System.out.println(original.getAddress() == copied.getAddress()); // true!关键在这里:引用指向同一个Address对象// 修改拷贝对象的引用类型成员copied.getAddress().setCity("上海");// 原始对象的address也被修改了!System.out.println(original.getAddress().getCity()); // 输出:上海}
}

2.2 深拷贝

2.2.1 方法一:重写 clone() 方法并递归克隆(常用)

// 1、让 Address 类也变得可克隆(实现 Cloneable 并重写 clone())
public class Address implements Cloneable {private String city;private String street;// ... 其他代码 ...@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}
}// 2、在 Person 的 clone() 方法中,不仅调用 super.clone(),还要手动克隆 address 字段
public class Person implements Cloneable {private String name;private int age;private Address address;// ... 其他代码 ...@Overridepublic Object clone() throws CloneNotSupportedException {// 1. 先调用super.clone()完成浅拷贝Person copied = (Person) super.clone();// 2. 对引用类型字段,手动调用其clone()方法进行深拷贝copied.address = (Address) this.address.clone();// 3. 返回深拷贝后的对象return copied;}
}// 3、测试
public class TestDeepCopy {public static void main(String[] args) throws CloneNotSupportedException {Address addr = new Address("北京", "长安街");Person original = new Person("张三", 25, addr);Person copied = (Person) original.clone();System.out.println(original.getAddress() == copied.getAddress()); // false!现在是不同的Address对象// 修改拷贝对象的addresscopied.getAddress().setCity("上海");// 原始对象的address不受影响System.out.println(original.getAddress().getCity()); // 输出:北京System.out.println(copied.getAddress().getCity());   // 输出:上海}
}

2.2.2 方法二:通过序列化实现(更强大,但更重)

前提:所有涉及到的类都必须实现 java.io.Serializable 接口。

import java.io.*;public class DeepCopyUtil {@SuppressWarnings("unchecked")public static <T extends Serializable> T deepCopy(T object) {try (ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos)) {// 将对象写入字节流oos.writeObject(object);oos.flush();try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais)) {// 从字节流中读出新的对象return (T) ois.readObject();}} catch (IOException | ClassNotFoundException e) {throw new RuntimeException("Deep copy failed", e);}}
}// Person和Address类都需要实现Serializable接口
public class Person implements Serializable { ... }
public class Address implements Serializable { ... }// 使用工具类进行深拷贝
Person original = new Person(...);
Person copied = DeepCopyUtil.deepCopy(original); // 完美的深拷贝

优点:无需关心对象内部复杂的引用结构,序列化机制会自动完成所有递归复制。
缺点:性能开销比 clone() 方法大;所有相关类都必须实现 Serializable 接口。

2.2.3 使用拷贝构造方法或拷贝工厂(推荐)

这是一种非常推荐的方式,它不依赖 Cloneable 接口,代码更清晰,也更灵活。

  • 拷贝构造方法:接受一个同一类型的参数,并据此构造一个新对象。

  • 拷贝工厂:一个静态方法,用于完成拷贝。

public class Person {private String name;private int age;private Address address;// 拷贝构造方法public Person(Person other) {this.name = other.name;this.age = other.age;// 对引用类型,调用其拷贝构造方法进行深拷贝this.address = new Address(other.address); // 假设Address也有拷贝构造方法}// 拷贝工厂 (静态方法)public static Person newInstance(Person other) {return new Person(other);}
}public class Address {private String city;private String street;// Address的拷贝构造方法public Address(Address other) {this.city = other.city;this.street = other.street;}
}// 使用
Person original = new Person(...);
Person copied = new Person(original); // 使用拷贝构造方法
// 或
Person copied2 = Person.newInstance(original); // 使用拷贝工厂

优点:代码意图明确,易于控制和扩展,是《Effective Java》推荐的方式。

http://www.xdnf.cn/news/18545.html

相关文章:

  • 探索 List 的奥秘:自己动手写一个 STL List✨
  • 基于JSqlParser的SQL语句分析与处理
  • 网址账号正确,密码错误返回的状态码是多少
  • Go语言数据结构与算法-基础数据结构
  • Compose笔记(四十七)--SnackbarHost
  • Axure:有个特别实用的功能
  • 什么是AI宠物
  • [2025CVPR-目标检测方向]PointSR:用于无人机视图物体检测的自正则化点监控
  • C++的struct里面可以放函数,讨论一下C++和C关于struct的使用区别
  • leetcode算法刷题的第十六天
  • 力扣热题之技巧
  • 雷卯针对香橙派Orange Pi 3G-IoT-B开发板防雷防静电方案
  • 云原生、容器及数据中心网络相关名词记录
  • 无人机光伏巡检误检率↓79%!陌讯多模态融合算法在组件缺陷检测的落地优化
  • 为什么存入数据库的中文会变成乱码
  • 浙江龙庭翔新型建筑材料有限公司全屋定制:畅享品质生活新境界!
  • 【小沐学GIS】基于C++绘制三维数字地球Earth(osgEarth、三维瓦片地球)第十期
  • 如何使用和优化SQL Server存储过程:全面指南
  • PETR/PETRv2
  • 从 M4S 到 MP4:用 FFmpeg 轻松合并音视频文件
  • C++矩阵类设计与实现:高效、健壮的线性代数工具
  • 2025年音乐创作大模型有哪些?国内国外模型汇总以及优点分析
  • 5G物联网的现实与未来:CTO视角下的成本、风险与破局点
  • Stm32通过ESP8266 WiFi连接阿里云平台
  • Spring Boot 校验分组(Validation Groups)高级用法全指南
  • 从0到1:数据库进阶之路,解锁SQL与架构的奥秘
  • 32位内部数据通路是什么?
  • 基于llama.cpp的量化版reranker模型调用示例
  • 【golang】制作linux环境+golang的Dockerfile | 如何下载golang镜像源
  • 避开MES实施的“坑”:详解需求、开发、上线决胜点