每日一博 - Java的Shallow Copy和Deep Copy

文章目录

  • 概述
  • 创建对象的5种方式
    • 1. 通过new关键字
    • 2. 通过Class类的newInstance()方法
    • 3. 通过Constructor类的newInstance方法
    • 4. 利用Clone方法
    • 5. 反序列化
  • Clone方法
  • 基本类型和引用类型
  • 浅拷贝
  • 深拷贝
  • 如何实现深拷贝
    • 1. 让每个引用类型属性内部都重写clone()方法
    • 2. 利用序列化

在这里插入图片描述

概述

关于Java的深拷贝和浅拷贝,简单来说就是创建一个和已知对象一模一样的对象。可能日常编码过程中用得不多,但了解深拷贝和浅拷贝的原理,对于Java中的值传递或者引用传递将会有更深的理解。


创建对象的5种方式

1. 通过new关键字

最常用的一种方式,通过new关键字调用类的有参或无参构造方法来创建对象。比如Object obj = newObject()


2. 通过Class类的newInstance()方法

这种默认是调用类的无参构造方法创建对象。比如Artisan p2 =(Artisan)Class. forName("com. ys.artisan.Artisan").newInstance()


3. 通过Constructor类的newInstance方法

和第2种方法类似,都是通过反射来实现的。通过java.lang. relect. Constructor类的newInstance()方法指定某个构造器来创建对象

  Artisan.class.getConstructor()[0].newInstance();

实际上第2种方法利用Class的newInstance()方法创建对 象,其内部调用还是Constructor的newInstance()方法。


4. 利用Clone方法

Clone是Object类中的一个方法,clone克隆顾名思义就是创建一个一模一样的对象出来。通过对象A. clone()方法会创建一个内容和对象A一模一样的对象B

Artisan a1 = new Artisan();
Artisan a2 = a1.clone();

5. 反序列化

序列化是把堆内存中的Java对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)​。

而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程


Clone方法

我们这里介绍Java的深拷贝和浅拷贝,其实现方式正是通过调用Object类的clone()方法来完成

   @IntrinsicCandidate
    protected native Object clone() throws CloneNotSupportedException;

这是一个用native关键字修饰的方法,关于native关键字,不理解也没关系,只需要知道用native修饰的方法就是告诉操作系统去实现。

具体过程不需要了解,只需要知道clone方法的作用就是复制对象并产生一个新的对象。那么这个新的对象和原对象是什么关系呢?


基本类型和引用类型

先拉齐一个概念,在Java中基本类型和引用类型的区别。

在Java中数据类型可以分为两大类:基本类型和引用类型。

  • 基本类型也称为值类型,分别是字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。
  • 引用类型则包括类、接口、数组、枚举等

Java将内存空间分为堆和栈。基本类型直接在栈中存储数值,而引用类型是将引用放在栈中,实际存储的值是放在堆中,通过栈中的引用指向堆中存放的数据。

基本类型和引用类型在JVM存储结构如图

在这里插入图片描述

  • a和b都是基本类型,其值是直接存放在栈中的;
  • 而c和d是String声明的,这是一个引用类型,引用地址是存放在栈中,然后指向堆的内存空间。
  • d = c;这条语句表示将c的引用赋值给d,那么c和d将指向同一块堆内存空间

浅拷贝

浅拷贝会复制对象的基本字段值,但对于对象中的引用类型字段,浅拷贝仅复制引用地址,而不会创建新的对象实例。即拷贝后的对象与原对象共享相同的引用类型数据。

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;
    }

    // 浅拷贝实现,使用Object.clone()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Address {
    String city;

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

使用

Person person1 = new Person("Alice", 25, new Address("New York"));
Person person2 = (Person) person1.clone();

// 修改 person2 的地址
person2.address.city = "Los Angeles";

// person1 的 address 也会被改变,因为浅拷贝复制的是引用地址
System.out.println(person1.address.city);  // 输出 "Los Angeles"

调用对象的clone方法,必须要让类实现Cloneable接口,并且重写clone方法

创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。


深拷贝

弄清楚了浅拷贝后,深拷贝就很容易理解了。深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当用户修改其中一个对象的任何内容时,都不会影响另一个对象的内容

深拷贝会递归地复制对象中的所有字段,包括引用类型字段所指向的对象。这样拷贝后的对象与原对象完全独立,互不影响。


如何实现深拷贝

深拷贝就是要让原始对象和克隆之后的对象所具有的引用类型属性不是指向同一块堆内存

1. 让每个引用类型属性内部都重写clone()方法

既然引用类型不能实现深拷贝,那么将每个引用类型都拆分为基本类型,分别进行浅拷贝。比如上面的例子,Person类有一个引用类型Address(其实String也是引用类型,但是String类型有点特殊)​,在Address类内部也重写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 = new Address(this.address.city);  // 递归复制引用类型字段
        return cloned;
    }
}

使用

Person person1 = new Person("Alice", 25, new Address("New York"));
Person person2 = (Person) person1.clone();

// 修改 person2 的地址
person2.address.city = "Los Angeles";

// person1 的 address 不会改变,因为深拷贝创建了独立的引用
System.out.println(person1.address.city);  // 输出 "New York"

这种做法有个弊端,这里Person类只有一个Address引用类型,而Address类没有,所以这里只重写Address类的clone方法,但是如果Address类也存在一个引用类型,那么也要重写其clone方法,这样有多少个引用类型,就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适


2. 利用序列化

序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在JVM中,所以可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

注意每个需要序列化的类都要实现Serializable接口,如果有某个属性不需要序列化,可以将其声明为transient,即将其排除在克隆属性之外

因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的

首先,确保要进行深拷贝的类和其引用的类都实现了Serializable接口

import java.io.*;

// 需要进行深拷贝的类必须实现 Serializable 接口
class Address implements Serializable {
    private static final long serialVersionUID = 1L;
    String city;

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

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                '}';
    }
}

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int age;
    Address address;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }

    // 深拷贝方法:使用序列化和反序列化
    public Person deepCopy() {
        try {
            // 将对象写入字节流
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.flush();

            // 从字节流读取对象
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (Person) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

public class DeepCopyExample {
    public static void main(String[] args) {
        // 创建原始对象
        Person person1 = new Person("Alice", 25, new Address("New York"));

        // 进行深拷贝
        Person person2 = person1.deepCopy();

        // 修改 person2 的地址
        if (person2 != null) {
            person2.address.city = "Los Angeles";
            person2.name = "Bob";
        }

        // 输出原对象和拷贝对象,验证深拷贝
        System.out.println("Original person1: " + person1);
        System.out.println("Copied person2: " + person2);
    }
}

输出结果:

Original person1: Person{name='Alice', age=25, address=Address{city='New York'}}
Copied person2: Person{name='Bob', age=25, address=Address{city='Los Angeles'}}

  • deepCopy()方法通过序列化将对象写入到ByteArrayOutputStream,再通过ObjectInputStream从字节流中读取对象,生成新的实例。
  • 修改person2的引用类型字段(如address.city)不会影响person1,从而验证了深拷贝的效果。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-25

文件下载与邀请翻译者 学习英特尔开发手册,最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册,会是一件耗时费力的工作。如果有愿意和我一起来做这件事的,那么&#xff…

Odoo :一款免费开源的日化行业ERP管理系统

文 / 开源智造Odoo亚太金牌服务 概述 构建以 IPD 体系作为核心的产品创新研发管控体系,增进企业跨部门业务协同的效率,支撑研发管控、智慧供应链、智能制造以及全渠道营销等行业的场景化,构筑行业的研产供销财一体化管理平台。 行业的最新…

【Golang】——Gin 框架中间件详解:从基础到实战

中间件是 Web 应用开发中常见的功能模块,Gin 框架支持自定义和使用内置的中间件,让你在请求到达路由处理函数前进行一系列预处理操作。这篇博客将涵盖中间件的概念、内置中间件的用法、如何编写自定义中间件,以及在实际应用中的一些最佳实践。…

计算机网络 (3)计算机网络的性能

一、计算机网络性能指标 速率: 速率是计算机网络中最重要的性能指标之一,它指的是数据的传送速率,也称为数据率(Data Rate)或比特率(Bit Rate)。速率的单位是比特/秒(bit/s&#xff…

云原生之运维监控实践-使用Telegraf、Prometheus与Grafana实现对InfluxDB服务的监测

背景 如果你要为应用程序构建规范或用户故事,那么务必先把应用程序每个组件的监控指标考虑进来,千万不要等到项目结束或部署之前再做这件事情。——《Prometheus监控实战》 去年写了一篇在Docker环境下部署若依微服务ruoyi-cloud项目的文章,当…

【C++】类中的“默认成员函数“--构造函数出现的意义?拷贝构造时“无穷递归”和“双重释放”出现的原因?

目录 "默认"成员函数 概念引入: 一、构造函数 问题引入: 1)构造函数的概念 2)构造函数实例 3)构造函数的特性 4)关于默认生成的构造函数 (默认构造函数) 默认构造函数未完成初始化工作实例: 二…

fastapi 调用ollama之下的sqlcoder模式进行对话操作数据库

from fastapi import FastAPI, HTTPException, Request from pydantic import BaseModel import ollama import mysql.connector from mysql.connector.cursor import MySQLCursor import jsonapp FastAPI()# 数据库连接配置 DB_CONFIG {"database": "web&quo…

基于微信小程序的乡村研学游平台设计与实现,LW+源码+讲解

摘 要 信息数据从传统到当代,是一直在变革当中,突如其来的互联网让传统的信息管理看到了革命性的曙光,因为传统信息管理从时效性,还是安全性,还是可操作性等各个方面来讲,遇到了互联网时代才发现能补上自…

基于Java Springboot城市交通管理系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 数…

手机直连卫星NTN通信初步研究

目录 1、手机直连卫星之序幕 2、卫星NTN及其网络架构 2.1 NTN 2.2 NTN网络架构 3、NTN的3GPP标准化进程 3.1 NTN需要适应的特性 3.2 NTN频段 3.3 NTN的3GPP标准化进程概况 3.4 NTN的3GPP标准化进程的详情 3.4.1 NR-NTN 3.4.1.1 NTN 的无线相关 SI/WI 3.4.1.2…

基本数据类型和包装类型的区别、缓存池、自动拆箱装箱(面试题)

目录 1. 八种基本类型及对应包装类型 2. 基本类型和包装类型 区别 3. 自动拆箱装箱 3.1 自动装箱 3.2 自动拆箱 3.3 缓存池 4. 高频面试案例分析 1. 八种基本类型及对应包装类型 基本数据类型类型描述范围(指数形式)位数包装类型byte整型&#x…

Aria2-CVE-2023-39141漏洞分析

前言: 在偶然一次的渗透靶机的时候,上网查询Aria2的历史漏洞,发现了这个漏洞,但是网上并没有对应的漏洞解释,于是我就就源代码进行分析,发现这是一个非常简单的漏洞,于是发这篇文章跟大家分享一…

androidstudio入门到放弃配置

b站视频讲解传送门 android_studio安装包:https://developer.android.google.cn/studio?hlzh-cn 下载安装 开始创建hello-world 1.删除缓存 文件 下载gradle文件压缩:gradle-8.9用自己创建项目时自动生成的版本即可,不用和我一样 https://…

河道无人机雷达测流监测系统由哪几部分组成?

在现代水利管理中,河道无人机雷达监测系统正逐渐成为一种重要的工具,为河道的安全和管理提供了强大的技术支持。那么,这个先进的监测系统究竟由哪几部分组成呢? 河道无人机雷达监测系统工作原理 雷达传感器通过发射电磁波或激光束…

Mac上详细配置java开发环境和软件(更新中)

文章目录 概要JDK的配置JDK下载安装配置JDK环境变量文件 Idea的安装Mysql安装和配置Navicat Premium16.1安装安装Vscode安装和配置Maven配置本地仓库配置阿里云私服Idea集成Maven 概要 这里使用的是M3型片 14.6版本的Mac 用到的资源放在网盘 链接: https://pan.baidu.com/s/17…

CKA认证 | Day3 K8s管理应用生命周期(上)

第四章 应用程序生命周期管理(上) 1、在Kubernetes中部署应用流程 1.1 使用Deployment部署Java应用 在 Kubernetes 中,Deployment 是一种控制器,用于管理 Pod 的部署和更新。以下是使用 Deployment 部署 Java 应用的步骤&#x…

ffmpeg编程入门

文章目录 ffmpeg流程常用的音视频术语常用概念复用器编解码器ffmpeg的整体结构注册组件相关封装格式相关函数的调用流程 相关的ffpmeg数据结构简介数据结构之间的关系 ffmpeg流程 图中的函数 以及结构体都是ffmpeg自带提供的 ffmpeg打开的时候 和其他io操作差不多 有一个类似句…

函数指针示例

目录&#xff1a; 代码&#xff1a; main.c #include <stdio.h> #include <stdlib.h>int Max(int x, int y); int Min(int x, int y);int main(int argc, char**argv) {int x,y;scanf("%d",&x);scanf("%d",&y);int select;printf(&q…

间接采购管理:主要挑战与实战策略

间接采购支出会悄然消耗掉企业的现金流&#xff0c;即使是管理完善的公司也难以避免。这是因为间接支出不直接关联特定客户、产品或项目&#xff0c;使采购人员难以跟踪。但正确管理间接支出能为企业带来显著收益——前提是要有合适的工具。本文将分享管理间接支出的关键信息与…

TCP(下):三次握手四次挥手 动态控制

欢迎浏览高耳机的博客 希望我们彼此都有更好的收获 感谢三连支持! TCP(上)&#xff1a;成熟可靠的传输层协议-CSDN博客 &#x1f95d;在上篇博客中&#xff0c;我们针对TCP的特性,报文结构,连接过程以及相对于其他协议的区别进行了探讨&#xff0c;提供了初步的理解和概览。本…