Java的对象克隆

这篇具有很好参考价值的文章主要介绍了Java的对象克隆。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本节我们会讨论 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

不过并没有这么简单。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 类的一个实例,就无需我们做任何处理了。)

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

  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 对象克隆文章来源地址https://www.toymoban.com/news/detail-418156.html

到了这里,关于Java的对象克隆的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 检查一个Java List是否包含某个JavaBean对象的特定值,并且获取这个值

    在这个例子中,我们使用了Java 8的 Optional 类来处理可能不存在的结果。如果找到了匹配的Bean对象,我们可以使用 get() 方法获取它,并打印出其名称。如果没有找到匹配的Bean对象, isPresent() 方法将返回 false ,我们将打印出相应的消息。

    2024年01月24日
    浏览(50)
  • 有点恐怖,这个工具能让你在 20 秒内,克隆你的声音,并转成英语!

    公众号关注 “GitHubDaily” 设为 “星标”,每天带你逛 GitHub! 在上学那会,我便一直有个想法,要是能有一个英汉自动翻译机,能把我说的话,直接翻译成英语就好了。这样,我就不需要在凌晨早起背单词语法,在操场上训练口语。 当我在「流浪地球」电影中,看到刘培强

    2024年02月08日
    浏览(47)
  • C++面向对象程序设计 - 类和对象进一步讨论

            在C++中,关于面向对象程序设计已经讲了很大篇幅,也例举很多案例,此篇将通过一些习题来进一步了解对象、静态成员、指针、引用、友元、类模板等等相关知识。         示例代码:         如上代码,运行后结果如下图:          现在将上述代码中,第

    2024年04月16日
    浏览(61)
  • Java进阶(4)——结合类加载JVM的过程理解创建对象的几种方式:new,反射Class,克隆clone(拷贝),序列化反序列化

    1.类什么时候被加载到JVM中,new,Class.forName: Class.forName(“包名.类名”); 2.创建对象的方式,反射,本质是获得类的类对象Class; 3.克隆clone,深拷贝,浅拷贝的对比; 4.序列化和反序列化的方式; Hello h; // 此时没有用Hello,jvm并没有进行类加载 看到new : new Book() Class.forName:

    2024年02月12日
    浏览(42)
  • 设计模式学习笔记 - 面向对象 - 1.面向对象到底讨论的是什么

    面向对象编程( OOP )的全称是 Object Oriented Programming 。 面向对象编程语言( OOPL )的全称是 Object Oriented Programming Language 。 面向对象编程中有两个非常重要的概念,就是类( Class )和对象( Object )。面向对象编程这个概念第一次使用是在 SmallTalk 这种编程语言中,它也被认

    2024年02月22日
    浏览(45)
  • 我们常说这个pycharm里有陷阱,第三方库导入失败,看这里!

    最近有小伙伴遇到了明明安装了 python 第三方库,但是在 pycharm 当中却导入不成功的问题。 ​ 一直以来,也有不少初学 python 的小伙伴,一不小心就跳进了虚拟环境和系统环境的【陷阱】中。 本文就基于此问题,来说说在 pycharm 当中如何使用系统环境、虚拟环境。 pycharm 当中

    2024年02月04日
    浏览(45)
  • Git fork了别人的仓库,别人的仓库有更新,我们如何同步这个更新

    要同步在GitHub上fork的别人的仓库的更新,可以按照以下步骤进行操作: 配置远程仓库地址: 在命令行中,进入你fork的仓库的本地目录,并添加一个指向原始仓库(即你fork的仓库来源)的远程仓库地址。通常,原始仓库会被称为\\\"upstream\\\"(上游仓库)。假设原始仓库的URL为

    2024年02月08日
    浏览(64)
  • 【java 基础二 】- 面向对象、类、接口等

    Java面向对象编程(OOP)是一种编程范式,其旨在通过将程序逻辑封装在对象中来使代码更易于理解和维护。Java是一种面向对象的编程语言,它支持封装、继承和多态等概念。以下是Java面向对象编程的核心概念: 对象(Object):对象是具有属性和行为的实体。在Java中,对象是通过

    2024年02月08日
    浏览(44)
  • 原型模式——对象的克隆

    可以通过一个原型对象克隆出多个一模一样的对象,该模式被称为原型模式。 在使用原型模式时,需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。 原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过克隆这些原型创建新

    2024年02月14日
    浏览(32)
  • 原型模式-克隆一个对象

     在开发一个界面的时候,里面有多个Button,这些对象的属性内容相似。如果一个个实例化Button对象,并设置其属性,那么代码量将会增多。 通过一个原型对象克隆出多个一模一样的对象,该模式被称为原型模式。  图 原型模式 Prototype: 抽象原型类,是声名克隆方法的接口,

    2024年02月16日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包