Java 的拷贝(深拷贝、浅拷贝)

news2024/11/18 9:34:47

在对象的拷贝中,很多初学者可能搞不清到底是拷贝了引用还是拷贝了对象。在拷贝中这里就分为引用拷贝、浅拷贝、深拷贝进行讲述。

引用拷贝

引用拷贝会生成一个新的对象引用地址,但是两个最终指向依然是同一个对象。如何更好的理解引用拷贝呢?很简单,就拿我们人来说,通常有个姓名,但是不同场合、人物对我们的叫法可能不同,但我们很清楚哪些名称都是属于 "我" 的!

 通过一个代码示例让大家领略一下 (为了简便就不写 get、set 等方法):

class Son {

    String name;
    int age;

    public Son(String name, int age) {

        this.name = name;
        this.age = age;
    }
}

public class test {
    
    public static void main(String[] args) {

        Son s1 = new Son("son1", 12);
        Son s2 = s1;
        s1.age = 22;
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1的age:" + s1.age);
        System.out.println("s2的age:" + s2.age);
        System.out.println("s1==s2" + (s1 == s2));//相等
    }
}

测试结果:

Son@135fbaa4
Son@135fbaa4
s1的age:22
s2的age:22
true

浅拷贝

如何创建一个对象,将目标对象的内容复制过来而不是直接拷贝引用呢?

这里先讲一下浅拷贝,浅拷贝会创建一个新对象,新对象和原对象本身没有任何关系,新对象和原对象不等,但是新对象的属性和老对象相同。具体可以看如下区别:

  • 如果属性是基本类型 (int,double,long,boolean 等),拷贝的就是基本类型的值;

  • 如果属性是引用类型,拷贝的就是内存地址(即复制引用但不复制引用的对象) ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

如果用一张图来描述一下浅拷贝,它应该是这样的:

如何实现浅拷贝呢?也很简单,就是在需要拷贝的类上实现 Cloneable 接口并重写其 clone () 方法

@Override
protected Object clone() throws CloneNotSupportedException {

    return super.clone();
}

 在使用的时候直接调用类的 clone () 方法即可。具体案例如下:

class Father{

    String name;
    public Father(String name) {

        this.name=name;
    }

    @Override
    public String toString() {

        return "Father{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Son implements Cloneable {
 
    int age;
    String name;
    Father father;

    public Son(String name,int age) {

        this.age=age;
        this.name = name;
    }

    public Son(String name,int age, Father father) {

        this.age=age;
        this.name = name;
        this.father = father;
    }

    @Override
    public String toString() {

        return "Son{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", father=" + father +
                '}';
    }

    @Override
    protected Son clone() throws CloneNotSupportedException {
        // 字段father指向同一个引用。
        return (Son) super.clone();
    }
}

public class test {
   
    public static void main(String[] args) throws CloneNotSupportedException {

        Father f=new Father("bigFather");
        Son s1 = new Son("son1",13);
        s1.father=f;
        Son s2 = s1.clone();
        
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1==s2:"+(s1 == s2));//不相等
        System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//相等
        System.out.println();

        //但是他们的Father father 和String name的引用一样
        s1.age=12;
        s1.father.name="smallFather";//s1.father引用未变
        s1.name="son222";//类似 s1.name=new String("son222") 引用发生变化
        System.out.println("s1.Father==s2.Father:"+(s1.father == s2.father));//相等
        System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//不相等
        System.out.println(s1);
        System.out.println(s2);
    }
}

运行结果:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:true//此时相等

s1.Father==s2.Father:true
s1.name==s2.name:false//修改引用后不等
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='smallFather'}}

不出意外,这种浅拷贝除了对象本身不同以外,各个零部件和关系和拷贝对象都是相同的,就好像双胞胎一样,是两个人,但是其开始的样貌、各种关系 (父母亲人) 都是相同的。需要注意的是其中 name 初始 == 是相等的,是因为初始浅拷贝它们指向一个相同的 String,而后 s1.name="son222" 则改变引用指向。

深拷贝

对于上述的问题虽然拷贝的两个对象不同,但其内部的一些引用还是相同的,怎么样绝对的拷贝这个对象,使这个对象完全独立于原对象呢?就使用我们的深拷贝了。深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。

 

在具体实现深拷贝上,这里提供两个方式,重写 clone () 方法和序列法。

重写 clone () 方法

如果使用重写 clone () 方法实现深拷贝,那么要将类中所有自定义引用变量的类也去实现 Cloneable 接口实现 clone () 方法。对于字符类可以创建一个新的字符串实现拷贝。

对于上述代码,Father 类实现 Cloneable 接口并重写 clone () 方法。son 的 clone () 方法需要对各个引用都拷贝一遍

//Father clone()方法
@Override
protected Father clone() throws CloneNotSupportedException {
   
    return (Father) super.clone();
}

//Son clone()方法
@Override
protected Son clone() throws CloneNotSupportedException {
   
    Son son= (Son) super.clone();//待返回克隆的对象
    son.name=new String(name);
    son.father=father.clone();
    return son;
}

 其他代码不变,执行结果如下:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:false

s1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

序列化

可以发现这种方式实现了深拷贝。但是这种情况有个问题,如果引用数量或者层数太多了怎么办呢?

不可能去每个对象挨个写 clone () 吧?那怎么办呢?借助序列化啊。

因为序列化后:将二进制字节流内容写到一个媒介(文本或字节数组),然后是从这个媒介读取数据,原对象写入这个媒介后拷贝给 clone 对象,原对象的修改不会影响 clone 对象,因为 clone 对象是从这个媒介读取。

熟悉对象缓存的知道我们经常将 Java 对象缓存到 Redis 中,然后还可能从 Redis 中读取生成 Java 对象,这就用到序列化和反序列化。一般可以将 Java 对象存储为字节流或者 json 串然后反序列化成 Java 对象。因为序列化会储存对象的属性但是不会也无法存储对象在内存中地址相关信息。所以在反序列化成 Java 对象时候会重新创建所有的引用对象。

在具体实现上,自定义的类需要实现 Serializable 接口。在需要深拷贝的类 (Son) 中定义一个函数返回该类对象:

 

protected Son deepClone() throws IOException, ClassNotFoundException {
   
      Son son=null;
      //在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组中
      //默认创建一个大小为32的缓冲区
      ByteArrayOutputStream byOut=new ByteArrayOutputStream();
      //对象的序列化输出
      ObjectOutputStream outputStream=new ObjectOutputStream(byOut);//通过字节数组的方式进行传输
      outputStream.writeObject(this);  //将当前student对象写入字节数组中

      //在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区
      ByteArrayInputStream byIn=new ByteArrayInputStream(byOut.toByteArray()); //接收字节数组作为参数进行创建
      ObjectInputStream inputStream=new ObjectInputStream(byIn);
      son=(Son) inputStream.readObject(); //从字节数组中读取
      return  son;
}

使用时候调用我们写的方法即可,其他不变,实现的效果为:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:false

s1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

注:2023/07/01 对重写clone方法示例。

package learnjavafx8;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

/**
 * @copyright 2023-2022
 * @package   learnjavafx8
 * @file      Person.java
 * @date      2023-07-01 14:34
 * @author    qiao wei
 * @version   1.0
 * @brief     
 * @history
 */
public class Person implements Cloneable {

	public static void main(String[] args) {
		Person p01 = new Person(10, 20, true);
		System.out.println(p01);
		
		Person p02 = (Person) p01.clone();
		p02.firstIntegerProperty.set(-233);
		p02.secondIntegerProperty.set(666);
		p02.flag = false;

		System.out.println(p01);
		System.out.println(p02);
	}
	
	public Person() {
		firstIntegerProperty = new SimpleIntegerProperty();
		secondIntegerProperty = new SimpleIntegerProperty();
		
		flag = false;
	}
	
	public Person(int first, int second, boolean flag) {
		firstIntegerProperty = new SimpleIntegerProperty(first);
		secondIntegerProperty = new SimpleIntegerProperty(second);
		
		this.flag = flag;
	}

	@Override
	protected Object clone() {
		Person person = null;
		
		try {
			person = (Person) super.clone();
			
			person.firstIntegerProperty =
				new SimpleIntegerProperty(this.firstIntegerProperty.get());
			person.secondIntegerProperty =
				new SimpleIntegerProperty(this.secondIntegerProperty.get());
			person.flag = this.flag;
		} catch (CloneNotSupportedException exception) {
			exception.printStackTrace();
		} finally {
			return person;
		}
	}

	@Override
	public String toString() {
		return "Person{" +
			"firstIntegerProperty=" + firstIntegerProperty +
			", secondIntegerProperty=" + secondIntegerProperty +
			", flag=" + flag +
			'}';
	}

	public IntegerProperty firstIntegerProperty = null;
	
	public IntegerProperty secondIntegerProperty = null;
	
	public boolean flag = false;
}

测试结果,对p02引用字段的修改并没有影响到p01引用字段。进行了深拷贝。

Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=false}

Process finished with exit code 0

修改Person类中clone方法,将深拷贝改为浅拷贝(删除引用类型数据copy内容),其余代码不变:

	@Override
	protected Object clone() {
		Person person = null;
		
		try {
			person = (Person) super.clone();
			
//			person.firstIntegerProperty =
//				new SimpleIntegerProperty(this.firstIntegerProperty.get());
//			person.secondIntegerProperty =
//				new SimpleIntegerProperty(this.secondIntegerProperty.get());
//			person.flag = this.flag;
		} catch (CloneNotSupportedException exception) {
			exception.printStackTrace();
		} finally {
			return person;
		}
	}

运行结果:

Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=false}

Process finished with exit code 0

p02引用字段的修改影响到p01的引用字段内容。

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

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

相关文章

工具类之打印表格ByList

前言 昨天在测试应用的时候,因为要查看数据库表里面的数据,但是又懒得去看数据库里面查找那张表,反而直接代码查看更方便。于是做了一个打印List的工具类,可以将List里面的数据进行打印成一个表格展示,List里面的元素…

CH543乐得瑞单C口显示器方案(LDR6020)

首先显示器的种类很多,有桌面显示器,便携显示器,智能显示器,甚至AR眼镜也可以算是一个微型显示器。以往的显示器传输视频信号多为VGA和HDMI,当然DP也有,只是占少数,再早之前还有模拟信号接口等等&#xff0…

牛客网基础语法91~100题

牛客网基础语法91~100题😘😘😘 💫前言:今天是咱们第九期刷牛客网上的题目。 💫目标:对短除法的使用,对函数的递归使用熟练。 💫鸡汤:绊脚石乃是进身之阶。先干…

手机便签软件推荐 有没有什么好用的手机便签软件app

在外出需要记事的时候,很多人会使用手机上的便签工具来完成。那手机便签软件推荐哪个比较好,有没有好用的手机便签软件app推荐呢?下面小编就为大家推荐一些用起来体验很不错的手机便签app软件。 一、敬业签 敬业签具备多种便签记事和提醒方式…

使用Spring Boot、MyBatis Plus和Elasticsearch配置的简单示例

下面是一个使用Spring Boot、MyBatis Plus和Elasticsearch的简单示例&#xff1a; 首先&#xff0c;在pom.xml文件中添加以下依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elast…

本地文件如何复制到服务器上 180.188.22.X

当我们在远程服务器时&#xff0c;有时会想要直接在本地电脑上复制文本到服务器上&#xff0c;或者把服务器上的文本文件复制到电脑上却无法进行操作。当我们遇到这样的情况&#xff0c;应该如何处理呢。系统如果是Windows的情况下&#xff0c;可以进行以下操作&#xff1a;打开…

【MySQL】不允许你不了解如何分组数据

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集&#xff01; &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指…

Nginx安装、卸载教程(含Window、Linux版、Docker版)

目录 一、下载 二、Linux版安装 2.1 编译安装之前 2.2 编译安装 2.3 启动Nginx 2.4 关于防火墙 2.5 安装成系统服务 三、Linux版卸载&#xff08;彻底&#xff09; 3.1 检查一下Nginx服务是否在运行 3.2 停止Nginx服务 3.3 查找、删除Nginx相关文件 3.4 再使用yum清…

C++11新特性(5):多线程

学习C11&#xff0c;根据网上资料的知识总结。 1. 线程创建 1.1 初始函数 #include <iostream> #include <thread> void myfunc(int &a) {cout << "a in myfunc:" << a << endl; }int main() {int a 1;std::thread mythread(…

Solr框架 02.Solr操作(document操作和query查询)

菜单项目Documents使用办法 其中的document选项&#xff1a; 以XML格式举例 1新增/修改 当id不存在时新增&#xff0c;当id存在修改。 <doc> <field name"id">8</field> <field name"name">明天更大卖</field> <field n…

好处多多的数仓分层是怎么样子的呢?如何创建数仓分层,

一、创建数仓分层 数仓分层是结合对业务场景、实际数据、使用系统的综合分析&#xff0c;对数据模型进行的整体架构设计及层级划分。用于将不同用途的数据&#xff0c;归类划分至不同的分层&#xff0c;便于您更好地组织、管理、维护数据。本文为您介绍如何创建并管理数仓分层…

Nik Dfine 降噪滤镜

Nik Define 是 Nik Collection 中专门用于降噪的滤镜。 Nik Dfine 有三种降噪方式&#xff1a;自动、手动以及精细局部控制。 大部分照片的降噪&#xff0c;用自动模式就可以达到满意效果。 有所侧重的话&#xff0c;可考虑手动降噪或精细局部控制方式降噪。 返回 Ps 后&#x…

JMeter分布式压测,启动执行机器报错: Port already in use: 1099

Problem creating registry: java.rmi.server.ExportException: Port already in use: 1099; nested exception is: java.net.BindException: Address already in use (Bind failed) 当压测量大的时候我们有时候会失败&#xff0c;然后再接着压测&#xff0c;这样就容易造成端口…

vue3框架开发uniapp高仿度小满金融App项目

vue3框架开发uniapp高仿度小满金融App项目 心血来潮写了度小满前端项目使用vue3开发地址&#xff1a;度小满金融 下面是实现效果

Jenkins + Docker + Maven + Windows 一键部署 Spring Boot 程序到远程 Linux 服务器

Jenkins Docker Maven Windows 一键部署 Spring Boot 程序到远程 Linux 服务器 文章目录 Jenkins Docker Maven Windows 一键部署 Spring Boot 程序到远程 Linux 服务器一、准备1、环境2、基本流程准备步骤基本思路 3、相关命令4、Dockerfile 文件5、配置远程服务器、安装…

外贸网站推广怎么做?英文外贸网站推广的好处

外贸网站推广是在全球范围内宣传和促销公司的产品或服务&#xff0c;以吸引国际客户和拓展海外市场。 以下是一些常见的外贸网站推广策略和方法&#xff1a; 网站优化&#xff1a;确保你的网站在搜索引擎上获得良好的排名。使用关键词研究和优化技术来提高网站的可见性和搜索引…

PostgreSQL中HOT对cluster的作用

PG中cluster的作用是根据表的索引重新构建一张表&#xff0c;并且表根据该索引进行排序&#xff0c;索引必须提前建好。 注意&#xff1a;cluster操作加ACCESS EXCLUSIVE锁&#xff0c;会阻塞其它任何操作。 我们为什么要运行cluster? PG中的表是堆表&#xff0c;表中行的顺…

SpringMvc接收前端发送的api请求参数问题笔记

SpringMvc接收前端发送的api请求参数问题笔记 get请求参数字符串日期转date接收 需要使用DateTimeFormat注解&#xff0c;来接收前端传的 http://xx.xx.xxx/xsdc?start2023-07-01 15:12:13&end2023-07-02 15:00:00 这种日期参数&#xff1b; 这样获取日期数据就能直接取…

启动一个qemu虚拟机

安装qemu&#xff1a; 编译内核&#xff1a; 编译rootfs&#xff1b; 启动&#xff1a; qemu-system-x86_64 -kernel ./linux-4.14.320/arch/x86_64/boot/bzImage -hda ./busybox-1.36.1/rootfs.img -append "root/dev/sda consolettyS0" -nographic 效果图&am…

乐优购物车微服务过滤拦截器

4.1.2.配置公钥 leyou:jwt:pubKeyPath: D:/heima/rsa/rsa.pub # 公钥地址cookieName: LY_TOKEN # cookie的名称 4.1.3.加载公钥 代码&#xff1a; ConfigurationProperties(prefix "leyou.jwt") public class JwtProperties { ​private String pubKeyPath;// 公…