java,深拷贝和浅拷贝

news2024/12/23 6:14:47

在 Java 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是对象拷贝的两种方式,主要区别在于它们如何处理对象的内部引用。

目录

一、浅拷贝(Shallow Copy)

实现方式

二、深拷贝(Deep Copy)

实现方式

1、手动深拷贝

2、通过序列化实现深拷贝

深拷贝中的注意事项

深拷贝的应用场景

总结


一、浅拷贝(Shallow Copy)

浅拷贝是指仅拷贝对象的基本类型字段引用类型字段的引用,而不是引用类型所指向的对象本身。因此,浅拷贝后的对象与原对象的引用类型字段共享相同的对象。如果原对象或拷贝对象中的引用类型被修改,两个对象都会受到影响。

实现方式

通过 clone() 方法实现浅拷贝。需要类实现 Cloneable 接口,并重写 clone() 方法。

class Person implements Cloneable {
    String name;
    int age;
    Address address;

    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // 浅拷贝
    }
}

class Address {
    String city;

    public Address(String city) {this.city = city;}
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person person1 = new Person("John", 25, address);
        Person person2 = (Person) person1.clone();  // 浅拷贝
        System.out.println(person1.address.city);  // 输出 "New York"
        System.out.println(person2.address.city);  // 输出 "New York"

        person2.address.city = "Los Angeles";
        System.out.println(person1.address.city);  // 输出 "Los Angeles",由于是浅拷贝,两者共享相同的地址引用
    }
}

在这个例子中,person1person2 共享同一个 Address 对象,修改 person2address 会影响 person1

二、深拷贝(Deep Copy)

深拷贝不仅拷贝对象的基本类型字段,还会递归地拷贝对象中所有的引用对象。因此,拷贝后的对象与原对象完全独立,互不影响。修改一个对象的引用类型字段不会影响另一个对象。

 深拷贝之所以能够实现对象的完全独立,关键在于它对对象中的引用类型字段进行了逐层递归的复制。其基本原理如下:

  • 基本数据类型的复制:对于 Java 中的基本数据类型(如 intdoublechar 等),深拷贝与浅拷贝是一样的,即直接拷贝数值。这是因为基本类型本质上是存储在栈中的固定长度的值,不涉及引用或指针。

  • 引用类型的处理:深拷贝时,引用类型(如对象、数组等)不会直接拷贝引用,而是会创建一个全新的副本,并且这个副本与原引用指向的对象互不相关。这种“递归”拷贝意味着如果对象内部还有对象,它们也会被逐一深拷贝。

实现方式

  • 手动实现:手动编写深拷贝逻辑,通过递归地调用 clone() 方法来拷贝所有引用对象。
  • 通过序列化实现:将对象序列化为字节流,再反序列化为新对象。

特点:

  • 基本类型字段会被完全复制。
  • 引用类型字段也会被完全复制,创建新的对象。
  • 深拷贝相比浅拷贝更耗时,因为需要递归复制所有引用类型。

1、手动深拷贝

手动实现深拷贝时,程序员需要遍历每个引用类型的字段,并递归调用 clone() 或其他方式来复制子对象。每个引用类型字段的深拷贝都需要单独处理。

以一个简单的对象层次结构为例:

class Person implements Cloneable {
    String name;
    int age;
    Address address;

    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) this.address.clone();  // 深拷贝 address
        return cloned;
    }
}

class Address implements Cloneable {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("New York");
        Person person1 = new Person("John", 25, address);
        Person person2 = (Person) person1.clone();  // 深拷贝

        System.out.println(person1.address.city);  // 输出 "New York"
        System.out.println(person2.address.city);  // 输出 "New York"

        person2.address.city = "Los Angeles";
        System.out.println(person1.address.city);  // 仍输出 "New York",因为进行了深拷贝,两个对象完全独立
    }
}

在这个例子中,Person 类中引用了 Address 对象。为了确保深拷贝,我们在 Person 类的 clone() 方法中显式地对 address 字段调用 clone() 方法,从而实现 Address 的深拷贝。这样 Person 对象和 Address 对象都是独立的。 

2、通过序列化实现深拷贝

如果对象和所有子对象实现了 Serializable 接口,可以使用序列化来实现深拷贝。

序列化是深拷贝的一种简便方式。通过将对象序列化为字节流,然后再将字节流反序列化为新的对象,可以实现深拷贝。这种方式适用于复杂的对象图,甚至对象之间有循环引用时也能正确处理。

通过序列化实现深拷贝的关键步骤如下:

  • 序列化:将对象及其所有引用对象通过字节流写入到一个存储介质中(例如 ByteArrayOutputStream)。
  • 反序列化:从存储介质中读取字节流,并重新构建对象及其引用对象,从而生成与原始对象完全独立的新对象。
import java.io.*;

class Person implements Serializable {
    String name;
    int age;
    Address address;

    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public Person deepCopy() throws IOException, ClassNotFoundException {
        // 序列化
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(this);

        // 反序列化
        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        return (Person) in.readObject();
    }
}

class Address implements Serializable {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Address address = new Address("New York");
        Person person1 = new Person("John", 25, address);
        Person person2 = person1.deepCopy();  // 深拷贝

        person2.address.city = "Los Angeles";
        System.out.println(person1.address.city);  // 输出 "New York",两个对象独立
    }
}

 在这个例子中,Person 对象和其引用的 Address 对象都实现了 Serializable 接口。通过序列化和反序列化,生成了完全独立的 PersonAddress 对象。

深拷贝中的注意事项

(1)对象之间的引用关系

深拷贝必须递归地处理对象中的所有引用类型,因此可能会遇到复杂的对象图结构。如果对象之间存在循环引用,序列化方案可以很好地处理这种情况,因为序列化机制会自动检测和处理对象循环引用的问题。

(2)性能成本

深拷贝的性能开销通常比浅拷贝大,尤其是当对象结构复杂时,递归地创建新的对象会消耗更多的时间和内存。

序列化虽然实现较为简便,但由于涉及字节流的创建和解析,性能相对较低。

(3)不可变对象

对于不可变对象(如 StringInteger 等),深拷贝和浅拷贝都无需特别处理,因为不可变对象在拷贝中不会受到修改的影响。

深拷贝的应用场景

(1)避免副作用:在需要确保对象之间完全独立,防止一个对象的修改影响另一个对象时,深拷贝是必需的。例如,当在多线程环境中使用对象时,使用深拷贝可以避免共享对象带来的数据不一致问题。

(2)复杂对象结构:当对象中嵌套了其他对象,并且这些对象可能会被修改时(例如树结构、图结构),深拷贝确保了各个层次的对象都能保持独立。

总结

  • 浅拷贝:拷贝对象的基本类型字段和引用类型的引用,两个对象共享相同的引用对象。
  • 深拷贝:完全独立的对象拷贝,包括对象的引用类型字段的递归拷贝。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2141107.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国际商城系统怎么弄 跨境电商商城怎样上线

国际商城系统一般涉及多个关键步骤。首先,需要选择合适的平台或开发工具,如商淘云国际电商系统或自定义开发。其次,系统应支持多语言、多币种以及国际支付网关,以满足全球客户的需求。第三,确保系统具有强大的物流和配…

推荐5款AI论文大纲生成器,一键极速生成!

在当今学术研究和写作领域,AI论文大纲生成器的出现极大地提高了写作效率和质量。以下是五款功能强大且全面的AI论文大纲生成器推荐: 一、千笔-AIPassPaper 千笔-AIPassPaper是一款基于深度学习和自然语言处理技术的AI写作助手,旨在帮助用户…

新160个crackme - 058-CZG-crackme1

运行分析 按下OK键后,程序退出 PE分析 C程序,32位,无壳 静态分析&动态调试 ida函数栏发现winMain(x,x,x,x),即打开窗口,双击函数跟进 继续跟进 双击DialogFunc函数,这个是窗口逻辑 继续跟进sub_401090函…

数据结构----栈和队列

(一)栈 1.栈的概念及结构 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First …

GFS 分布式文件系统 GlusterFS

一、GlusterFS概述 1.1、GlusterFS简介 GlusterFS 是一个开源的分布式文件系统。由存储服务器、客户端以及NFS/Samba 存储网关(可选,根据需要选择使用)组成。 包括其去中心化(无元数据服务器)的特性,这有…

【苍穹外卖】总结

1 pom 依赖 1.1 MyBatis Spring 用于简化 MyBatis 与 Spring Boot 的集成,提供了对 MyBatis 框架的自动配置支持,简化了数据访问层的开发 1.2 Lombok Lombok 是一个 Java 库,能够通过注解自动生成常见的代码(如 getter、setter、…

双亲委派机制知识点

类加载器 双亲委派模型 为什么采用双亲委派模型 打破双亲委派机制的场景 Tomcat 打破双亲委派机制:目的是可以加载不同版本的jar包 实现类隔离:在Tomcat中,每个Web应用使用独立的类加载器加载类文件,这样做的好处在于,当在同一T…

C++二叉搜索树(二叉树进阶)

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 C二叉搜索树(二叉树进阶) 收录于专栏 [C进阶学习] 本专栏旨在分享学习C的一点学习笔记,欢迎大家在评论区交流讨论💌 目录 1. 二叉搜索树…

Java重修笔记 第五十七天 坦克大战(七)多线程基础 - 编程练习

1. 线程之间的协调控制(通知方式) public class Homework04 {public static void main(String[] args) {// 在 main 方法中启动两个线程// 第一个线程内循环打印 1 到 100 以内的整数// 直到第二个线程从键盘读取到 "Q" 指令后结束第一个线程…

Porcupine - 语音关键词唤醒引擎

文章目录 一、关于 Porcupine特点用例尝试一下 语言支持性能 二、Demo1、Python Demo2、iOS DemoBackgroundService DemoForegroundApp Demo 3、网页 Demo3.1 Vanilla JavaScript 和 HTML3.2 Vue Demos 三、SDK - Python 一、关于 Porcupine Porcupine 是一个高度准确和轻量级…

LC并联电路在正弦稳态下的传递函数推导(LC并联谐振选频电路)

LC并联电路在正弦稳态下的传递函数推导(LC并联谐振选频电路) 本文通过 1.解微分方程、2.阻抗模型两种方法推导 LC 并联选频电路在正弦稳态条件下的传递函数,并通过仿真验证不同频率时 vo(t) 与 vi(t) 的幅值相角的关系。 电路介绍 已知条件…

Axure RP实战:打造高效图形旋转验证码

Axure RP实战:打造高效图形旋转验证码 在数字产品设计的海洋中,验证码环节往往是用户交互体验的细微之处,却承载着验证用户身份的重要任务。 传统的文本验证码虽然简单直接,但随着用户需求的提高和设计趋势的发展,它…

vue2的diff算法

Vue2 的虚拟 DOM diff 算法是一种高效的算法,用于比较新旧两个虚拟 DOM 树,找出差异并更新到真实 DOM 上。这个算法的核心在于尽量减少不必要的 DOM 操作,提高性能。 虚拟dom:把DOM数据化,先通过不断地操作数据&#…

如何在手机端跑大模型?

最近新入手了一台 arm 开发板,内置安装了 Android 13 系统。 昨天把网络问题给解决了:安卓连接 WIFI 但无法上网?盘点踩过的那些坑 今日分享,继续带大家实操:如何把大模型(LLM)部署到移动端&a…

文章资讯职场话题网站源码整站资源自带2000+数据

介绍: 数据有点多,数据资源包比较大,压缩后还有250m左右。值钱的是数据,网站上传后直接可用,爽飞了 环境:NGINX1.18 mysql5.6 php7.2 代码下载

JUC学习笔记(三)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 八、共享模型之工具--JUC8.1 AQS 原理1. 概述2 实现不可重入锁自定义同步器自定义锁 3.心得起源目标设计1) state 设计2)阻塞恢复设计3)队列…

学习笔记 韩顺平 零基础30天学会Java(2024.9.16)

P563 自定义泛型方法 当调用方法时,要传入参数,因为当传入参数时,编译器就可以确定泛型代表的类型 泛型方法和方法使用了泛型是不一样的 泛型方法可以使用类声明的泛型,也可以使用自己的泛型 P564 泛型方法练习 P565 泛型的继承和…

Python编码系列—Python适配器模式:无缝集成的桥梁

🌟🌟 欢迎来到我的技术小筑,一个专为技术探索者打造的交流空间。在这里,我们不仅分享代码的智慧,还探讨技术的深度与广度。无论您是资深开发者还是技术新手,这里都有一片属于您的天空。让我们在知识的海洋中…

二叉树OJ题——另一棵树的子树

文章目录 一、题目链接二、解题思路三、解题代码 一、题目链接 另一棵树的子树 题目描述:判断当前树A是否是树B的子树。 二、解题思路 时间复杂度:O(n*m) 三、解题代码

Learn ComputeShader 15 Grass

1.Using Blender to create a single grass clump 首先blender与unity的坐标轴不同,z轴向上,不是y轴 通过小键盘的数字键可以快速切换视图,选中物体以后按下小键盘的点可以将物体聚焦于屏幕中心 首先我们创建一个平面,宽度为0.2…