当前位置: 首页 > news >正文

每日一博 - 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()方法来完成

   @IntrinsicCandidateprotected 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()@Overrideprotected 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;}// 深拷贝实现@Overrideprotected 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;}@Overridepublic 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;}@Overridepublic 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,从而验证了深拷贝的效果。

在这里插入图片描述

http://www.lryc.cn/news/486158.html

相关文章:

  • .netcore + postgis 保存地图围栏数据
  • 【AI图像生成网站Golang】项目介绍
  • 对称加密算法DES的实现
  • Spring Boot 启动时修改上下文
  • 传奇996_19——常用函数
  • 计算机毕业设计Python+Neo4j知识图谱医疗问答系统 大模型 机器学习 深度学习 人工智能 大数据毕业设计 Python爬虫 Python毕业设计
  • 【Python】如何设置VSCode中的Pylint,消除各种没有必要的警告
  • 游戏引擎学习第14天
  • 关于mysql中的锁
  • 机器学习-4:机器学习的建模流程
  • Android 6年经验面试总结 2024.11.15
  • R语言数据分析可视化——summarytools包的使用
  • 转型一年半,虎牙直播的第二增长曲线喜忧参半
  • makefile笔记
  • Rewar Model的输出(不包含训练)
  • Python调用API翻译Excel中的英语句子并回填数据
  • SQL面试题——抖音SQL面试题 最大在线用户数
  • 前端知识点---Window对象(javascript)了解
  • llama factory lora 微调 qwen2.5 7B Instruct模型
  • 类和对象——拷贝构造函数,赋值运算符重载(C++)
  • Android 关于使用videocompressor库压缩没有声音和异常的问题
  • LeetCode-215.数组中的第K个最大元素
  • 『OpenCV-Python』视频的读取和保存
  • 什么是Spring Boot Actuator
  • 计算机网络:运输层 —— 运输层端口号
  • linux下编译安装memcached
  • 最短路径生成树的数量-黑暗城堡
  • 将已有的MySQL8.0单机架构变成主从复制架构
  • JSON.stringify的应用说明
  • pyflink datastream数据流ds经过一系列转换后转为table,t_env.from_data_stream(ds)