Java的对象克隆

news2025/1/26 15:43:38

本节我们会讨论 Cloneable 接口,这个接口指示一个类提供了一个安全的 clone() 方法。

Object 类提供的 clone() 方法是 “浅拷贝”,并没有克隆对象中引用的其他对象,原对象和克隆的对象仍然会共享一些信息。深拷贝指的是:在对象中存在其他对象的引用的情况下,会同时克隆对象中引用的其他对象,原对象和克隆的对象互不影响。

介绍克隆

要了解克隆的具体含义,先来回忆为一个包含对象引用的变量建立副本时会发生什么。原变量和副本都是同一个对象的引用(见图 6-1)。这说明,任何一个变量改变都会影响另一个变量。

Employee original = new Employee("John Public", 50000);
Employee copy = original;
copy.raiseSalary(lO); // oops-also changed original

如果希望 copy 是一个新对象,它的初始状态与 original 相同,但是之后它们各自会有自己不同的状态,这种情况下就可以使用 clone() 方法。

Employee copy = original.clone();
copy.raiseSalary(lO); // OK original unchanged

image-20230418124610231.png


不过并没有这么简单。clone() 方法是 Object 的一个 protected 方法,这说明你的代码不能直接调用这个方法。只有 Employee 类可以克隆 Employee 对象(Object 类不可以克隆 Employee 类)。这个限制是有原因的。想想看 Object 类如何实现 clone()。Object 类它对于这个对象一无所知,所以只能逐个域地进行拷贝。如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题、但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。

class Employee {
    // instance fields
    private String name;
    private double salary;
    private Date hireDay;

    // constructor
    public Employee(String n, double s, int year, int month, int day) {
        this.name = n;
        this.salary = s;
        this.hireDay = new Date(year, month, day);
    }

    // a method
    public String getName() {
        return name;
    }
    // more methods
}

图 6-2 显示了使用 Object 类的 clone() 方法克隆一个 Employee 对象会发生什么。可以看到,默认的克隆操作是 “浅拷贝”,并没有克隆对象中引用的其他对象。

浅拷贝会有什么影响吗?这要看具体情况。

  • 如果原对象和浅克隆对象共享的子对象是不可变的,那么这种共享就是安全的。如果子对象属于一个不可变的类,如 String,就是这种情况。或者在对象的生命周期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的(子对象虽然是可变的,但是在在对象的生命周期中,子对象的数据域没有发生改变)。
  • 不过,通常子对象都是可变的,必须重新定义 clone() 方法来建立一个深拷贝,同时克隆所有子对象。在这个例子中,hireDay 域是一个 Date,这是可变的,所以它也需要克隆。(出于这个原因,这个例子使用 Date 类型的域而不是 LocalDate 来展示克隆过程。如果 hireDay 是不可变的 LocalDate 类的一个实例,就无需我们做任何处理了。)

image-20230418124817828.png


对于每一个类,需要确定:

  1. 默认的 clone() 方法是否满足要求;
  2. 是否可以在可变的子对象上调用 clone() 方法来修补默认的 clone() 方法;
  3. 是否不该使用 clone() 方法

实际上第 3 个选项是默认选项。如果选择第 1 项或第 2 项,类必须:

  • 实现 Cloneable 接口;
  • 重新定义 clone() 方法,并指定 public 访问修饰符。

Cloneable 接口

Cloneable 接口的出现与接口的正常使用并没有关系。具体来说,Cloneable 接口没有指定 clone() 方法,clone() 方法是从 Object 类继承的。Cloneable 接口只是作为一个标记,指示类设计者了解克隆过程。对象对于克隆很 “偏执”,如果一个对象请求克隆,但没有实现 Cloneable 接口,就会生成一个受检异常(CloneNotSupportedException 异常)。

如果在一个对象上调用 clone() 方法,但这个对象的类并没有实现 Cloneable 接口,Object 类的 clone() 方法就会拋出一个 CloneNotSupportedException。

注释:Cloneable 接口是 Java 提供的一组标记接口(tagging interface)之一。应该记得:

  • Comparable 等接口的通常用途是确保一个类实现一个或一组特定的方法。
  • 标记接口不包含任何方法,标记接口唯一的作用就是允许在类型查询中使用 instanceof:if (obj instanceof Cloneable) {}

建议你自己的程序中不要使用标记接口。


即使 clone() 的默认(浅拷贝)实现能够满足要求,还是需要实现 Cloneable 接口,将 clone() 方法重新定义为 public, 再调用 super.clone()。下面给出一个例子:

class Employee implements Cloneable {
    // raise visibility level to public, change return type
    public Employee clone() throws CloneNotSupportedException {
        return (Employee) super.clone();
    }
}

与 Object.clone() 提供的浅拷贝相比,前面看到的 clone() 方法并没有为它增加任何功能。这里只是让这个方法是公有的。要建立深拷贝,还需要做更多工作,克隆对象中可变的实例域。下面来看创建深拷贝的 done() 方法的一个例子:

class Employee implements Cloneable {
    // ...
    public Employee clone() throws CloneNotSupportedException {
        // call Object.clone()
        Employee cloned = (Employee) super.clone();
        // clone mutable fields
        cloned.hireDay = (Date) hireDay.clone();
        return cloned;
    }
}

如果在一个对象上调用 clone() 方法,但这个对象的类并没有实现 Cloneable 接口,Object 类的 clone() 方法就会拋出一个 CloneNotSupportedException。当然,Employee 和 Date 类实现了 Cloneable 接口,所以不会抛出这个异常。不过, 编译器并不了解这一点,因此,我们声明了这个异常。


捕获这个异常是不是更好一些?

public Employee clone() {
    try {
        Employee cloned = (Employee) super.clone();
        // ...
        return cloned;
    } catch (CloneNotSupportedException e) {
        return null;
    }
    // this won't happen, since we are Cloneable
}

捕获异常这非常适用于 final 类。否则,最好还是保留 throws 说明符。这样就允许子类在不支持克隆时选择抛出一个 CloneNotSupportedException()。

子类的克隆

必须当心子类的克隆。例如,一旦为 Employee 类定义了 clone() 方法,任何人都可以用它来克隆 Manager 对象(Manager 类是 Employee 类的子类)。Employee 克隆方法能完成工作吗?这取决于 Manager 类的域。在这里是没有问题的,因为 bonus 域是基本类型。但是 Manager 可能会有需要深拷贝或不可克隆的域。不能保证子类的实现者一定会修正 clone() 方法让它正常工作。出于这个原因, 在 Object 类中 clone() 方法声明为 protected。不过,如果你希望类用户调用 clone(),就不能这样做。

参考资料

《Java核心技术卷一:基础知识》(第10版)第 6 章:接口、lambda 表达式与内部类 6.2.3 对象克隆

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

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

相关文章

微服务---一篇学完SpringCloud

SpringCloud 1.认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢? 1.0.学习目标 了解微服务架构的优缺点 1.1.单体架构 单体架构&#xff1a…

java企业级信息系统开发学习笔记06基于xml配置方式使用Spring MVC

文章目录 一、学习目标二、Spring MVC概述1、MVC架构2、Spirng MVC3、使用Spring MVC的两种方式 三、基于xml配置与注解的方式使用Spring MVC(一)创建Maven项目(二)添加相关依赖(三)给项目添加Web功能&…

SpringMVC表格提交中文乱码和配置logback

最佳解决方案还是使用Spring提供的过滤器&#xff0c;将其配置到WEB.XML文件中&#xff1a; <filter><filter-name>characterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class&g…

nginx部署VUE项目

前言 目前公司的前端代码基本都是部署在nginx下&#xff0c;特此来记录一下 开发环境&#xff1a;window10 nginx环境搭建&#xff08;参考下方文章&#xff09; window环境安装 mac环境安装 本地我将nginx放置于F盘 前端项目打包 一个nginx服务下可能会放置多个前端包&…

echarts 折线图

Echarts 常用各类图表模板配置 注意&#xff1a; 这里主要就是基于各类图表&#xff0c;更多的使用 Echarts 的各类配置项&#xff1b; 以下代码都可以复制到 Echarts 官网&#xff0c;直接预览&#xff1b; 图标模板目录Echarts 常用各类图表模板配置一、简洁折线图二、环形图…

结构体的存储

由于要想知道结构体的大小&#xff0c;了解结构体是如何存储在内存中的 我们需要先了解一个知识点&#xff1a; 结构体内存对齐 1. 第一个成员在与结构体变量偏移量为0的地址处 (偏移量是某个字节相较于起始存储空间的相差字节数 例如第一个字节的偏移量是0&#xff0c;第二个…

一套专业的C#医院体检管理系统源码 PEIS体检报告管理系统源码 C/S医院PEIS系统源码

医院PEIS体检管理系统源码&#xff0c;有源码&#xff0c;有演示&#xff0c;自主研发&#xff0c;官方正版授权&#xff01; 开发语言&#xff1a;C# 开发工具&#xff1a;VS2013版本起 后端框架&#xff1a;winform 数 据 库&#xff1a;oracle 12c 医院体检系统主要特点…

人大金仓亮相2023CHITEC,五大看点不容错过

近日&#xff0c;由中国卫生信息与健康医疗大数据学会和《中国卫生信息管理杂志》社联合举办的2023&#xff08;17th&#xff09;中国卫生信息技术/健康医疗大数据应用交流大会暨软硬件与健康医疗产品展览会&#xff08;2023 CHITEC&#xff09;在安徽合肥顺利召开。 作为数据库…

【DAY38】BOM/VUE初步学习

pageXOffset 设置或返回当前页面相对于窗口显示区左上角的 X 位置。 pageYOffset 设置或返回当前页面相对于窗口显示区左上角的 Y 位置。 screenLeft&#xff0c;screenTop&#xff0c;screenX&#xff0c;screenY 声明了窗口的左上角在屏幕上的的 x 坐标和 y 坐标。IE、Safari…

JavaScript历史

JavaScript历史 参考视频1 1990年&#xff0c;第一个终端显示网页被蒂姆博士创造出来&#xff0c;表现为超链接跳转、无图的特点。文本格式定义、文本传输协议即应用层协议&#xff0c;解析显示引擎是关键。1993年&#xff0c;随着人们对视觉效果的要求逐渐变高&#xff0c;马…

Https详解

文章目录 一. 什么是 Https1. "加密"是什么?2. 对称加密3. 非对称加密4. "中间人攻击" 二. 引入证书理解签名黑客能否伪造证书?黑客能否替换公钥?黑客能否篡改签名?如何查看证书? 一. 什么是 Https https 就是 http 安全层(SSL)–> 用来加密的协…

黑马在线教育数仓实战6

6. 意向用户主题看板_增量流程 6.1 数据采集(拉链表) 7. hive的索引 ​ 索引的作用: 加快查询的效率 为什么索引可以提升查询效率呢? hive索引是在 分区 分桶优化基础上, 又提供一种新的优化手段, 如果分区 和分桶受限, 可以尝试使用索引的方式来优化处理 hive提供了三种索…

VMware ESXi 8.0U1 macOS Unlocker OEM BIOS (标准版和厂商定制版)

ESXi 8.0U1 标准版&#xff0c;Dell HPE 联想 浪潮 定制版 请访问原文链接&#xff1a; https://sysin.org/blog/vmware-esxi-8-u1-oem/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 2023-04-18, VMware vSphere 8.0U1 发布…

家用洗地机实用吗?家用洗地机款式推荐

要说现在家居清洁用什么单品更省心&#xff0c;洗地机必须要算一项。虽然这在国际上也不是什么新鲜的概念了&#xff0c;但是在国内兴起也只是这几年的事&#xff0c;关于家用洗地机什么牌子最好之类的问题也是很多人都比较关心的问题。我个人也是不喜欢做家务的&#xff0c;家…

C++算法:排序、查找

排序 排序是一个非常经典的问题&#xff0c;它以一定的顺序对一个数组&#xff08;或一个列表&#xff09;中的项进行重新排序 有许多不同的排序算法&#xff0c;每个都有其自身的优点和局限性。 时间复杂度&#xff1a;对排序数据的总的操作次数。反映当n变化时&#xff0c;操…

SQL之SQL优化

文章目录 一、插入数据优化insert优化大批量插入数据 二、主键优化数据组织方式页分裂页合并主键设计原则三、order by优化 四、Group By 优化五、limit优化六、count优化count的几种用法 七、update优化总结 一、插入数据优化 insert优化 insert into tb_test values(1, tom…

Linux:centos:系统服务基础控制(systemctl)基础使用 图形化工具ntsysv使用

基础使用的办法为&#xff1a; systemctl控制类型服务名称 控制常用类型为一下几个 start 启动 stop 停止 enable 开机自启 disable 开机不自启 restart 重新启动 reload 重新加载 status 查看服务状态 systemc…

Redis---主从复制

一、redis主从复制 主从复制&#xff1a;是存储数据的服务结构 主服务器&#xff1a;接受客户端连接的服务器 从服务器&#xff1a;自动与主服务器保持数据一致的服务器 配置主从复制 1、环境准备 主服务器 主机名&#xff1a;master IP地址&#xff1a;192.168.11.101/…

在PyCharm中配置Git

防止以后换软件或电脑忘记怎么配置PyCharm&#xff0c;记录一下。 前提 电脑已经安装好了Git工具&#xff0c;安装教程有一个GitHub账号&#xff08;这不废话嘛…&#xff09;电脑最好有科学上网工具&#xff08;要不然拉取、推送等操作总是不成功&#xff09; PyCharm设置 …

VMware安装苹果系统教程 MAC安装VMware Tools,开启拖拽功能

VMware虚拟机安装苹果系统教程 1 准备工作 安装VM虚拟机、准备Install_macOS_Monterey_12.5 镜像、darwin1012.iso &#xff08;VMware Tools&#xff09;、unlocker解锁工具 2 解锁VM虚拟机 默认VM虚拟机是不支持macOS安装的&#xff0c;我们需要对虚拟机进行解锁操作&…