首页 > 编程语言 >Java的对象克隆

Java的对象克隆

时间:2023-04-19 09:02:11浏览次数:39  
标签:Java 克隆 对象 clone 接口 Cloneable Employee

本节我们会讨论 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 对象克隆

标签:Java,克隆,对象,clone,接口,Cloneable,Employee
From: https://www.cnblogs.com/feiyu2/p/17332006.html

相关文章

  • Java-Day-12( 类变量 + 类方法 + main 方法 + 代码块 + 单例设计模式 + final 关键字 )
    Java-Day-12类变量定义一个变量count,是一个类变量(静态变量)staticclassPerson{privateStringname;// 该静态变量static最大的特点就是会被Person所有的对象实例共享publicstaticintcount=0;publicPerson(Stringname){this.na......
  • 类与对象的定义
    1.对象是一个·类的实例2.任何一个对象只能属于一个具体的类3.类与对象和关系与数据类型和变量的关系相似4.类成员的默认访问属性是private5.无参且无返回值的类成员函数show的函数原型:   void Show()const;6.类的成员函数之间可以互相调用 使用类计算矩形的面积。......
  • 每日八股文之Java
    1、请你说说多线程得分点:线程和进程的关系、为什么使用多线程进程是操作系统资源调度的基本单位,线程是处理器任务调度和执行的基本单位,一个进程可以创建多个线程,每个线程有自己独立的程序计数器,本地方法栈和虚拟机栈,线程之间共享进程的堆和方法区。线程之间是通过时间片算法......
  • Java包
    包包是一组类和接口的集合。包的引入,实现了封装特性。同一个包中不允许有同名的类和接口,不同的包中允许同名的类和接口。包的引入,解决了类名的冲突问题。包本身也是分级的,包中还可以有子包。Java的包可以用文件系统来存放,也可以存放在数据库中。在Windows中,包是以文件系统来......
  • java学习日记20230414-HashSet源码
    HashSetHashSet底层是HashMap添加一个元素时,先得到Hash值,会转化成索引值;找到存储数据表table,看这个索引位置是否存放元素;如果没有直接加入如果有,调用equals比较,如果相同放弃添加,如果不同,则添加到最后在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8)(table表......
  • JavaScript程序与设计入门到入土
    4.JavaScript代码的书写位置和css一样,我们的js也可以有多种方式书写在页面上让其生效js也有多种方式书写,分为行内式,内嵌式,外链式4-1行内式JS代码(不推荐)写在标签上的js代码需要依靠事件(行为)来触发<!--写在a标签的href属性上--><ahref="javascript:alert('我是......
  • 【Java】构造方法
    如果想在创建对象时就能完成属性的初始化操作,给属性赋相应的值,可通过类的特殊成员——构造方法(也称为构造函数)完成。构造方法可用于当对象被创建时初始化对象中的属性。构造方法时一个特殊的方法,它的名字必须与所在的类的名字相同,且没有返回类型。语法:【访问符】<类名>(【参数列......
  • java异常处理
    Java异常处理异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出java.lang.ArithmeticException的异......
  • 【Java技术指南】「Unirest编程专题」一起认识一下一个“灰常”优秀的Http工具,让Http
    Unirest-Java是一个轻量级的HTTP客户端库,它提供了简单易用的API,可以帮助Java开发人员快速地发送HTTP请求和处理响应。在本文中,我们将深入探讨Unirest-Java的技术细节和使用方法。Unirest-Java的优点简单易用:Unirest-Java提供了一组简单易用的API,可以帮助Java开发人员快速地发送HTTP......
  • Java异常
    一、理论部分1、Java异常架构与异常关键字Java异常简介Java异常是Java提供的一种识别及响应错误的一致性机制。Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what,where,why这3个......