Java 中的浅拷贝和深拷贝

news2024/11/18 19:40:14

开发过程中,有时会遇到把现有的一个对象的所有成员属性拷贝给另一个对象的需求。这个时候就会用到拷贝这个概念。把原对象定义成 A,拷贝后的对象定义成 B,如果只是单纯使用 clone 方法进行拷贝,你会发现:

  1. 对于八个基本类型,会拷贝其值,并且 B 的改变不会影响 A。

  2. 如果是一个对象,拷贝的是地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。B 对该值的改变会影响 A。

  3. 对于 String字符串,这个比较特殊,虽然拷贝的也是引用,但是在修改的时候,它会从字符串池中重新生成新的字符串,原有的字符串对象保持不变。

这种只单纯拷贝引用地址的动作就是浅拷贝。

相反,如果拷贝一个对象时不是简单的将地址引用拷贝出来,而是新建了一个对象,这种方式就是深拷贝。

浅拷贝代码模拟

通过代码模拟浅拷贝的过程:

首先,新建两个实体类,学生和老师:

public class Teacher {
    private int id;
    private String name;
    public Teacher(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }
    public void setId(int id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

接下来是学生,学生的实体类需要实现 clone

public class Student implements Cloneable {
    private int id;
    private String name;
    private Teacher teacher;
    public Student(int id, String name, Teacher teacher) {
        this.id = id;
        this.name = name;
        this.teacher = teacher;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

接下来就来看一下浅拷贝的效果

public static void main(String[] args) throws CloneNotSupportedException {
    //新建一个student1
    Student student1=new Student(1,"javayz",new Teacher(1,"teacher1"));
    //student2从student1中克隆过去
    Student student2= (Student) student1.clone();
    //修改基本类型 int
    student2.setId(2);
    //修改String
    student2.setName("javayz2");
    //修改对象类型teacher
    Teacher teacher = student2.getTeacher();
    teacher.setName("teacher2");
    System.out.println(student1); //{id=1,name='javayz',teacher=Teacher{id=1,name='teacher2'}}
    System.out.println(student2); //{id=2,name='javayz2',teacher=Teacher{id=1,name='teacher2'}}
}

在这里插入图片描述
通过结果就可以发现,修改被克隆对象的基本类型和 String 类型不会对原来数据造成影响,但是由于用的是同一个引用地址,修改对象时两边都会被修改。

深拷贝代码模拟

深拷贝的其中一个方法是把被拷贝对象中的所有引用类型也都实现深拷贝,最后逐层拷贝实现引用地址是新的而不是用的同一个。

修改上面的 teacher 对象代码,实现 clone 方法

public class Teacher implements Cloneable{
    private int id;
    private String name;
    //省略构造方法、get、set、toString方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

修改 student 类的 clone 方法

@Override
protected Object clone() throws CloneNotSupportedException {
    Student student = (Student) super.clone();
    student.teacher= (Teacher) teacher.clone();
    return student;
}

然后执行同样的测试代码后就会发现两个对象已经互相不影响了。

第二个方法是利用 serializable 实现深拷贝,这种方式的原理在于通过 IO 流的方式先将序列化后的对象写进 IO 流中,再取出来实现深拷贝。这种方式下所有涉及到的类都必须实现 Serializable 接口

新建一个 DeepStudent 类,同时需要使得 Teacher 类也实现 Serializable 接口

public class DeepStudent implements Serializable {
    private static final long serialVersionUID=1L;
    private int id;
    private String name;
    private Teacher teacher;
    //省略构造方法、get、set、toString方法
    public Object deepCopy(){
        try {
            //将对象写到IO流中
            ByteArrayOutputStream bos=new ByteArrayOutputStream();
            ObjectOutputStream oos=new ObjectOutputStream(bos);
            oos.writeObject(this);
            //再从IO流中获取到对象
            ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois=new ObjectInputStream(bis);
            return ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

编写一个测试方法:

    public static void main(String[] args) throws CloneNotSupportedException {
       //新建一个student1
        DeepStudent student1=new DeepStudent(1,"javayz",new Teacher(1,"teacher1"));
        //student2从student1中克隆过去
        DeepStudent student2= (DeepStudent) student1.deepCopy();
        //修改基本类型 int
        student2.setId(2);
        //修改String
        student2.setName("javayz2");
        //修改对象类型teacher
        Teacher teacher = student2.getTeacher();
        teacher.setName("teacher2");
        System.out.println(student1);
        System.out.println(student2);
    }

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

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

相关文章

基于unity物体定点移动与模拟刹车的细节 GIF 图文详解——线性差值函数以及平滑阻尼的运用和实践(Lerp AND SmoothDamp)

👨‍💻个人主页:元宇宙-秩沅 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 秩沅 原创 收录于专栏 [unity常用API] ⭐相关文章:基础不牢,地动山摇系列 ------ 软硬通吃 unity常用API ⭐…

Arduino与Proteus仿真实例-TFT LCD绘制酷炫曲线图表

TFT LCD绘制酷炫曲线图表 本文将介绍如何在ILI9341 TFT LCD中绘制酷炫曲线图表仿真。 在前面的文章中,对ILI9341的驱动仿真作了详细的介绍,请参考: Arduino与Proteus仿真实例-TFT显示屏(ILI9341驱动器)SPI驱动仿真1、仿真电路原理图 2、仿真代码实现 本次实例使用到如下开…

23种设计模式(一)——单例模式【对象性能】

文章目录意图什么时候使用单例单例模式的实现1、有缺陷的懒汉式2、线程安全、内存安全的懒汉式单例 (智能指针,锁)3、最推荐的懒汉式单例([magic](https://so.csdn.net/so/search?qmagic&spm1001.2101.3001.7020) static )——局部静态变…

Wondershare 有哪些不错的办公软件呢

第一款 Wondershare UniConverter14 Wondershare UniConverter中文学习版(万兴优转)是一款国产全能视频格式转换软件.万兴格式转换器最新版具有音视频格式转换,合并视频,视频压缩,视频编辑,视频录制,视频下载,视频元数据修复,VR视频转换,字幕编辑器,GIF制作,DVD刻录等一站式视…

天线参数介绍

1.天线辐射元件种类图1-1 天线辐射元件种类如图1-1所示,天线辐射元件种类有:电镀偶极子、印刷电路偶极子、裂缝波导、凹口辐射器、矩形贴片辐射器、开口波导等。2、天线参数2.1 天线方向图天线方向图又叫辐射方向图(radiation pattern&#x…

【MySQL基础教程】多表查询

前言 本文为 【MySQL基础教程】多表查询 相关知识,下边将对多表关系,多表查询概述,内连接,外连接,自连接,子查询,多表查询案例等进行详尽介绍~ 📌博主主页:小新要变强 的…

分布式理论协议与算法 第一弹 CAP理论

CAP 理论,也被称为 CAP 协议,指的是在一个分布式系统中,最多只能同时满足「一致性(Consistency)」、「可用性(Availability)」和「分区容错性(Partition tolerance)」这三…

Vue3 中组合式下的侦听器

目录前言:前置代码:创建侦听器停止侦听器watch侦听:对象中的某一个属性变化(需要提供getter函数才行)watch侦听:整个对象watch侦听:如果嵌套属性发生改变,默认是无法执行回调函数的?watch侦听:…

【实战与杂谈】如何复活一个开源网站-游戏王卡片生成器

1. 杂谈 作为一名十多年游戏王玩家,学生时代玩的是PSP和PS2上的游戏,到毕业后使用YGOPRO同步新卡片进行联网对战,再到现在约到线下进行实体卡片游戏。有些卡片价格太贵,因此我们对于这些卡在未购买之前都会自己打印出来暂时游玩…

sec5-属性

1 属性 GObject系统提供属性。属性是由实例保存的值,实例是GObject的后代,它们对其他实例开放。可以通过他们的名字访问他们。 例如,GtkWindow具有"title"、“default-width”、"default-height"等属性。字符串"t…

正经科普:DDos高防ip详解

白衬衫容易发黄, 一般洗衣液很难洗掉, 不少人为此感到头疼, 不妨在洗的时候吃点头痛药。 这边我也不多废话,大家直接看图,高防ip原理如下清洗能力 DDoS高防IP采用BGP链路对接全国各地30家运营商,总防御能力超4T。采用电信云堤近源…

智能优化算法:人工兔优化算法-附代码

智能优化算法:人工兔优化算法 摘要:人工兔优化算法( [Artificial rabbits optimization,RSO)是 Liying Wang等 于 2022 年提出的一种新型元启发式优化算法 。 该算法受来源于自然界中兔子的生存策略的启发,具有寻优能力强&#x…

【2.1】服务拆分--案例Demo

服务拆分--案例Demo服务拆分注意事项:导入服务拆分Demo测试结果:总结知识内容来自于黑马程序员视频教学和百度百科。博主仅作笔记整理便于回顾学习。如有侵权请私信我。 服务拆分注意事项: 比如现在有一个需求,是查询订单&#x…

国产GPU芯片突破重围,迎来新发展,中国崛起的力量

最近,在GPU芯片领域我们终于迎来新进展,有望突破欧美企业垄断,实现完全国产化。高端GPU芯片对科技发展有着重要作用,广泛应用于云上服务、高密度高性能计算等领域,然而绝大部分GPU芯片市场都被英伟达、AMD、微软等国外…

骨传导耳挂式耳机排名前十名,最好的骨传导耳机推荐

挂耳式的骨传导耳机在佩戴时更舒适以及更加牢固,日常在多种场景使用都能完全兼顾。但是最好的骨传导耳机有哪些呢?还不知道如何选择骨传导耳机,可以看看这五款骨传导耳机~ 1、南卡Runner Pro4骨传导蓝牙耳机 ¥1498 选择骨传导耳机…

Kubernetes那点事儿——控制器Deployment

K8s应用程序生命周期管理——控制器Deployment一、部署应用程序流程二、Deployment控制器1、应用升级、弹性伸缩、回滚、删除2、滚动升级、回滚机制3、定义Deployment前言说到K8s程序的生命周期管理我们不得不提到k8s的控制器。其中Deployment是最为常用的controllers&#xff…

Liga妙谈 | 如何快速甄别、高效响应用户反馈?

敏捷开发说要「拥抱变化」,在充满不确定的环境中,唯一不变的正是变化。面对源源不断的市场反馈和需求变更,敏捷团队应该如何平衡「高效迭代」与「响应用户」的关系,既快又好地完成研发任务,交付业务价值? …

FFmpeg 滤镜详解

FFmpeg Filter 1. 概念介绍 在多媒体处理中,术语滤镜(filter)指的是修改未编码的原始音视频数据帧的一种软件工具。 2. 基本原理 ● 在编码前,ffmpeg可以对raw(真实/原)音频和视频使用libavfilter库中的滤镜进行处理。(非压缩…

骨感传导蓝牙耳机怎么样、骨感传导蓝牙耳机有什么特点

在正文开始前,先跟大家说明一下,骨感传导其实就是我们常说的骨传导,两者是相同的意思,只是表达的文字不太一样。我们可以理解为骨感传导耳机骨传导耳机,那既然是这样,骨传导耳机又是利用什么原理传播声音的…

CentOS搭建web服务器,并内网穿透实现公网访问

在web项目中,部署的web站点需要被外部访问,则需要一个媒介,通过把资源放在这个媒介中,再通过所暴露的端口指向这个站点,当外部访问这个媒介所对应的端口时,媒介指向站点,完成访问,像这种类似的媒介,常用的有tomcat容器、Apache等,这边使用Apache来建搭建。 Apache2 是一种流行…