Java 拷贝

news2025/2/27 4:51:36

Java 中的拷贝分为两种,浅拷贝和深拷贝,关于为什么要有两种拷贝方式而不是一种,就要涉及到 Java 的两种类型数据了。Java 的深浅拷贝都是针对于引用类型而言的,基本类型是没有深浅拷贝之分的,类似于 C++ 语言,浅拷贝中的引用有点像是 C++ 语言中指针。

一、浅拷贝

浅拷贝只会拷贝引用类型的引用,而并不会拷贝整个引用对象的全部内容。也就是说,拷贝过程中,只有引用对象在栈上的引用被复制了一份,其指向的内容并没有被复制,因此当新浅拷贝的对象内容被修改时,原来对象的内容也会被同时修改。

Java 中的浅拷贝默认通过 Object 类的 clone 方法实现:

import java.util.ArrayList;

class ShallowCopyExample implements Cloneable { // 自定义类必须实现 Cloneable 接口
    public int num = 0; // 基本类型
    public String string = "shallow copy"; // 引用类型
    public ArrayList<Integer> arrayList = new ArrayList<>(); // 引用类型

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 直接用父类的,不做任何修改,为浅拷贝
    }
}

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        ShallowCopyExample shallowCopyExample = new ShallowCopyExample();
        ShallowCopyExample copiedObject = (ShallowCopyExample) shallowCopyExample.clone(); // 浅拷贝
        copiedObject.num = 1;
        copiedObject.string = "copied"; // String 不可修改,指向一个新的 String 对象
        copiedObject.arrayList.add(1); // 修改 arrayList 本身
        System.out.println(shallowCopyExample.num); // Output: 0(未修改)
        System.out.println(shallowCopyExample.string); // Output: shollow copy(未修改)
        System.out.println(shallowCopyExample.arrayList); // Output: [1](已修改)
    }
}

从上面的例子可以看出,浅拷贝会将对象的引用类型给同步修改,因为改的是引用对象的内容,并未改变引用本身。但上面的 String 也是引用对象,为何没有被更改呢?实际上面 copiedObject 的 String 类型字段是直接修改其引用,而非引用的内容,因为 String 类型不可更改,看似是引用对象的内容,实则为引用本身。

总结一下:浅拷贝会将对象的全部内容都拷贝过来,但是对于其引用对象的内容,只会拷贝它们的引用,而不会拷贝它们的内容。

这就相当于 C 语言的指针,复制了指针,但指针指向的那片内存空间并没有复制。

以字段的拷贝为例,给个理解图就是:

浅拷贝过程理解图

Java 的浅拷贝实际上和 Python 的拷贝非常相似,Python 的内置类 list 便是如此,浅拷贝相当于一个新的引用,或者说别名。

二、深拷贝

浅拷贝是 Java 本身就有的,但有些时候我们不想要浅拷贝的这种效果,我们想要彻底完全的拷贝,也就是在拷贝引用对象的引用的同时,将其内容也给拷贝一份,以此来完全除去拷贝对象对原来对象的影响。我们可以用上面类似浅拷贝的理解图,以字段的拷贝为例来展现深拷贝:

深拷贝过程理解图

 

要达到这种效果,就必须实现深拷贝,也就是彻底的拷贝,但是很遗憾,Java 并没有像 Object 对象的 clone 方法那么方便的工具来实现深拷贝,想要实现深拷贝,可以采用的方法有:

  • 重写 clone 方法以实现深拷贝;
  • 采用序列化与反序列化的方式来达到类似深拷贝的效果;

简单来说:深拷贝就是完全复制,无论是值类型还是引用类型。

2.1 重写 clone 方法

因为深拷贝必须使每一层的引用对象都被完全拷贝,因此拷贝过程中涉及到的每个引用类型都必须重写它们的 clone 方法(并非简单地继承父类 clone 方法)来实现深拷贝。下面是一个重写的案例:

import java.util.ArrayList;

class ShallowCopyExample implements Cloneable {
    public int num = 0; // 基本类型
    public String string = "shallow copy"; // 引用类型
    public ArrayList<Integer> arrayList = new ArrayList<>(); // 引用类型

    @Override
    protected Object clone() throws CloneNotSupportedException {
        ShallowCopyExample shallowCopyExample = (ShallowCopyExample) super.clone();
        shallowCopyExample.arrayList = new ArrayList<>(arrayList); // 手动将 arrayList 复制一份
        return shallowCopyExample;
    }
}

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        ShallowCopyExample shallowCopyExample = new ShallowCopyExample();
        ShallowCopyExample copiedObject = (ShallowCopyExample) shallowCopyExample.clone();
        copiedObject.num = 1;
        copiedObject.string = "copied"; // String 不可修改,指向一个新的 String 对象
        copiedObject.arrayList.add(1); // 修改 arrayList 本身
        System.out.println(shallowCopyExample.num); // Output: 0(未修改)
        System.out.println(shallowCopyExample.string); // Output: shollow copy(未修改)
        System.out.println(shallowCopyExample.arrayList); // Output: [](未修改)
    }
}

从上面的代码中我们可以看出,深拷贝的实现并不是很难,但是当一个类中包含非常多不同类型的引用类型,每一个都要重写那将会是一件非常麻烦且繁琐的事情,这个时候,我们就要采取第二种方式了,也就是序列化。

2.2 序列化与反序列化

Java 序列化是一种将对象转换为字节流的过程,以便可以将对象保存到磁盘上,将其传输到网络上,或者将其存储在内存中,以后再进行反序列化,将字节流重新转换为对象。注意每个需要序列化的类都要实现 Serializable 接口。

下面是一个序列化与反序列化的案例:

import java.io.*;
import java.util.ArrayList;

class ShallowCopyExample implements Serializable { // 序列化对象必须实现 Serializable 接口
    public int num = 0; // 基本类型
    public String string = "deep copy"; // 引用类型
    public ArrayList<Integer> arrayList = new ArrayList<>(); // 引用类型

    public ShallowCopyExample deepClone() {
        // 序列化
        try (
                FileOutputStream fileOutputStream = new FileOutputStream("object.ser"); // 对象文件为 object.ser
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        ) {
            objectOutputStream.writeObject(this);
        } catch(IOException ioException) {
            ioException.printStackTrace();
        }

        // 反序列化
        try (
                FileInputStream fileInputStream = new FileInputStream("object.ser");
                ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        ) {
          return (ShallowCopyExample) objectInputStream.readObject();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        // 失败返回 null
        return null;
    }
}

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        ShallowCopyExample shallowCopyExample = new ShallowCopyExample();
        ShallowCopyExample copiedObject = shallowCopyExample.deepClone(); // 改用自己定义的,用序列化实现的深拷贝方法
        copiedObject.num = 1;
        copiedObject.string = "copied"; // String 不可修改,指向一个新的 String 对象
        copiedObject.arrayList.add(1); // 修改 arrayList 本身
        System.out.println(shallowCopyExample.num); // Output: 0(未修改)
        System.out.println(shallowCopyExample.string); // Output: deep copy(未修改)
        System.out.println(shallowCopyExample.arrayList); // Output: [](未修改)
    }
}

因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。用下面一张图可以很清晰地了解这一过程:

序列化与反序列化

序列化这一方法不仅仅在 Java 中有,在其他编程语言中同样拥有。实际上,序列化的主要作用并非深拷贝,而是用于将对象持久化,从而用于网络传输等。Python 中的内置模块 pickle 就是序列化相关的模块,然而 Python 中的深拷贝却无需使用 pickle 模块来实现,因为其内置模块 copy 中有一个 deepcopy 深拷贝函数,可以非常方便地得到大部分对象的深拷贝,希望有朝一日 Java 也能拥有如此方便的内置工具,而不再需要手动重写 clone 方法了。 

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

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

相关文章

C# AnimeGAN 漫画风格迁移 动漫风格迁移 图像卡通化 图像动漫化

效果 项目 模型 animeganv3_H40_model.onnx animeganv3_H50_model.onnx animeganv3_H64_model.onnx AnimeGANv3_JP_face_v1.0.onnx AnimeGANv3_PortraitSketch_25.onnx Hayao-60.onnx Hayao_64.onnx Paprika_54.onnx Shinkai_53.onnx 下载 可执行文件exe下载 源码下载

element el-table树形表格结构,勾选联动 父级勾选,子级全部选中,勾选子级,父级显示勾选状态

需求实现如下 重点使用到 select 以及 select-all 两个方法 返回数据格式 代码实现 <el-table ref"tableRef" :cell-style"{ color: #FFF, background: #333 }":header-cell-style"{ color: #FFF, background: #333 }" row-key"id"…

操作系统 面试题(二)

PART1 1.乐观锁和悲观锁的区别是什么&#xff1f; 2.乐观锁和悲观锁的实现方式分别有哪些&#xff1f; 3.平均周转时间如何计算&#xff1f; 4.银行家算法是什么&#xff1f; 5.IO多路复用是什么&#xff1f; 6.HTTP的头部包含哪些内容&#xff1f; 7.TCP如何保持连接&a…

HTTP 原理与CND原理

1 HTTP 原理 HTTP是一个无状态的协议。无状态是指客户机&#xff08;Web浏览器&#xff09;和服务器之间不需要建立持久的连接&#xff0c;这意味着当一个客户端向服务器端发出请求&#xff0c;然后服务器返回响应(response)&#xff0c;连接就被关闭了&#xff0c;在服务器端…

NSA 和 CISA 揭示十大网络安全错误配置

美国国家安全局 (NSA) 和网络安全与基础设施安全局 (CISA) 在5日公布了其红蓝团队在大型组织网络中发现的十大最常见的网络安全错误配置。 通报还详细介绍了威胁行为者使用哪些策略、技术和程序 (TTP) 来成功利用这些错误配置来实现各种目标&#xff0c;包括获取访问权限、横向…

内存空间扩充之进程覆盖技术,交换技术

1.覆盖技术 人们引入了覆盖技术&#xff0c;用来解决“程序大小超过物理内存总和”的问题。 一个固定区&#xff1a; 存放最活跃的程序段固定区中的程序段在运行过程中不会调入调出 若干覆盖区&#xff1a; 不可能同时被访问程序段可共享一个覆盖区覆盖区中的程序段在运行…

MySQL慢查询的原因与解决方案

一、前言 在数据库系统中&#xff0c;慢查询是一个常见的问题。特别是在MySQL中&#xff0c;由于其复杂的查询结构和大量的数据&#xff0c;慢查询可能会导致系统性能下降&#xff0c;甚至影响整个应用的运行。本文将详细介绍MySQL慢查询的原因&#xff0c;并提供一些有效的解…

基于springboot实现火车订票管理系统项目【项目源码+论文说明】计算机毕业设计

摘要 本论文主要论述了如何使用JAVA语言开发一个火车订票管理系统 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述火车订票管理系统的当前背景以及系统开发的…

免备案域名 DNS解析

1、注册购买域名地址&#xff08;免备案&#xff09; Name.com | Domain Names, Registration, Websites & Hosting 注意国家选中国区才可以使用支付宝&#xff0c;信息填写完之后创建账号&#xff0c;邮箱填写自己的邮箱 2、注册完登录账号进去找到Domain Name 搜索自动…

SOLIDWORKS® 2024 新功能 - 3D CAD

1、 先前版本的兼容性 • 利用您订阅的 SOLIDWORKS&#xff0c;可将您的 SOLIDWORKS 设计作品保存为旧版本&#xff0c;与使用旧版本 SOLIDWORKS 的供应商无缝协作。 • 可将零件、装配体和工程图保存为新版本前两年之内的SOLIDWORKS 版本。 优点&#xff1a; 即使其他用户正…

信息系统项目管理师第四版学习笔记——高级项目管理

项目集管理 项目集管理角色和职责 在项目集管理中涉及的相关角色主要包括&#xff1a;项目集发起人、项目集指导委员会、项目集经理、其他影响项目集的干系人。 项目集发起人和收益人是负责承诺将组织的资源应用于项目集&#xff0c;并致力于使项目集取得成功的人。 项目集…

Lego Studio打开solidworks零件/装配体 (sw另存obj文件)

solidworks 2020 Lego studio / part designer 截至2023-10-13&#x1f382;最新版 文章目录 操作步骤1&#xff09; solidworks 开启 ScanTo3D 功能2&#xff09; 零件 / 装配体 保存至stl格式文件3&#xff09; 以SanTo3D网格文件方式打开stl4&#xff09; 将打开的stl另存为…

港联证券:三季报亮相 盈利拐点来了?

10月12日&#xff0c;跟着黄山胶囊宣布深市2023年首份三季报&#xff0c;以及华辰装备宣布创业板首份三季报&#xff0c;第一批宣布三季报的A股公司增至5家。 Wind数据闪现&#xff0c;到10月12日记者发稿时&#xff0c;A股共有208家上市公司宣布了本年三季度成果预告&#xf…

基于 ACK Fluid 的混合云优化数据访问(五):自动化跨区域中心数据分发

作者&#xff1a;车漾 前文回顾&#xff1a; 本系列将介绍如何基于 ACK Fluid 支持和优化混合云的数据访问场景&#xff0c;相关文章请参考&#xff1a; -基于 ACK Fluid 的混合云优化数据访问&#xff08;一&#xff09;&#xff1a;场景与架构 -基于 ACK Fluid 的混合云优…

用wpf替代winform 解决PLC数据量过大页面卡顿的问题

winform 由于不是数据驱动, 页面想刷新数据必须刷新控件, wpf则不用. 可以利用wpf 的数据绑定和IOC, 页面中的消息传递, itemscontrol 实现大量数据刷新, 上位机页面不卡顿 代码如下: <Windowx:Class"NavTest.Views.NewMainView"xmlns"http://schemas.micr…

Linu:【Kafka四】集群介绍与单机搭建

目录 环境简介 一、搭建kafka集群 1.1、复制出两个kafka的配置文件 1.2、修改配置文件中的如下属性 二、启动kafka集群 三、可校验kafka三个节点是否均启动成功 四、查看集团中主题的分区和副本 4.1、新建一个包含了分区和副本的主题 4.2、查看该主题的详细信息 五、…

EDI入门讲解——一篇文章告诉你EDI是什么

知行软件从2008年开始做EDI解决方案&#xff0c;多年来帮助国内企业建立起与交易伙伴的EDI连接&#xff0c;在国内市场已有较高的市场份额。 我们在国内有自己的研发、实施和运维团队。如果您在产品使用过程中有任何问题、有任何功能上的需求、或者在我们产品中发现了错误&…

Talk | SIGGRAPH‘23 Best Paper 秦颖思:分罗曼三维显示器—各点独立变焦显示技术

本期为TechBeat人工智能社区第537期线上Talk。 北京时间10月12日&#xff08;周四&#xff09;20:00&#xff0c;卡耐基梅隆大学博士生—秦颖思的Talk已准时在TechBeat人工智能社区开播&#xff01; 她与大家分享的主题是: “分罗曼三维显示器—各点独立变焦显示技术”&#xf…

C++入门1

C入门1 1.前言2.命名空间1.C语言对于命名空间方面的缺陷2.命名空间的语法特性1.域作用限定符2.命名空间的可嵌套性 3.声明与定义分离的命名空间4.命名空间的展开5.多个命名空间中命名冲突6.对于命名空间的推荐写法 3.iostream1.cout和endl2.cin 3.缺省参数1.缺省参数的形式2.缺…

从零开始学习调用百度地图网页API:二、初始化地图,鼠标交互创建信息窗口

目录 代码结构headbodyscript 调试 代码 <!DOCTYPE html> <html> <head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><meta name"viewport" content"initial-scale1.0, user-scalable…