本文参考

本篇文章参考自《Effective Java》第三版第十三条"Always override toString",在《阿里巴巴Java开发手册》中也有对clone方法规约:

【推荐】慎用 Object的 clone方法来拷贝对象。
  说明:对象clone 方法默认是浅拷贝,若想实现深拷贝需覆写clone 方法实现域对象的深度遍历式拷贝。

关于深拷贝和浅拷贝的见解,还可以参考此文章:https://www.jianshu.com/p/94dbef2de298

creates objects without calling a constructor

实例通过调用clone()方法创建一份一模一样的拷贝,在这过程中没有调用构造方法

但是注意,拷贝有浅拷贝和深拷贝之分,在原文中并没有对浅拷贝和深拷贝的区别进行详细的区分

a class implementing Cloneable is expected to provide a properly functioning public clone method

Cloneable接口内没有定义任何的方法,实现它仅仅是为了改变超类Object中受保护的clone()方法的行为,使得它能够真正做到field-for-field拷贝

A class implements the Cloneable interface to indicate to the Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.

倘若我们在没有实现Cloneable接口的情况下重载clone()方法,则会在调用时抛出CloneNotSupportedException异常(属于checkedException检查异常)

Invoking Object's clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown.

相反的,倘若我们仅仅只是实现Cloneable接口,而没有重载clone()方法,也无法做到实例的拷贝

Note that this interface does not contain the clone method. Therefore, it is not possible to clone an object merely by virtue of the fact that it implements this interface. Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.

因此,我们要保证类既实现了Cloneable接口,也重载了clone()方法

By convention, classes that implement this interface should override Object.clone (which is protected) with a public method.

the clone method functions as a constructor; you must ensure that it does no harm to the original object and that it properly establishes invariants on the clone

默认的clone()方法调用super.clone()时仅仅只是采用浅拷贝,若当前类的字段为基本数据类型或指向不可变实例的引用,则不用考虑深拷贝的问题(有一种例外情况,某个不可变的字段代表了实例的唯一ID,则需要进行深拷贝)

如果引用了可变的对象,例如数组可以调用本身的clone()方法来实现一份拷贝,其它的类实例则要求对应的类也重载了clone()方法

注意,Cloneable架构与引用可变对象的final域的正常用法是互不兼容的

the Cloneable architecture is incompatible with normal use of final fields referring to mutable objects

下面是代码实例

public class CloneExample implements Cloneable {

  private String str;

  private int intPrimitive;

  private int[] intArray;

  public CloneExample(String str, int intPrimitive, int[] intArray) {

    this.str = str;

    this.intPrimitive = intPrimitive;

    this.intArray = intArray;
  }

  public void setStr(String str) {

    this.str = str;
  }

  public void setIntPrimitive(int intPrimitive) {

    this.intPrimitive = intPrimitive;
  }

  public void setIntArray(int[] intArray) {

    this.intArray = intArray;
  }

  @Override

  public boolean equals(Object o) {

    if (this == o) return true;

    if (o == null || getClass() != o.getClass()) return false;

    CloneExample example = (CloneExample) o;

    if (intPrimitive != example.intPrimitive) return false;

    if (!str.equals(example.str)) return false;

    return Arrays.equals(intArray, example.intArray);
  }

  @Override

  public int hashCode() {

    int result = str.hashCode();

    result = 31 * result + intPrimitive;

    result = 31 * result + Arrays.hashCode(intArray);

    return result;
  }

  @Override

  public String toString() {

    return "CloneExample{" +

           "str='" + str + '\'' +

           ", intPrimitive=" + intPrimitive +

           ", intArray=" + Arrays.toString(intArray) +

           '}';
  }
  /**
   *
协变返回类型
   * /
  @Override

  protected CloneExample clone() throws CloneNotSupportedException {

    CloneExample example = (CloneExample) super.clone();

    /*
     *
如果intArray是final,则此处无法再进行拷贝赋值
     * 因为super.clone()调用后,已经为final字段进行了浅拷贝赋值

     */

    example.intArray = intArray.clone();

    example.str = String.valueOf(str.toCharArray());

    return example;
  }
}

下面是测试类

@Test
public void test() throws CloneNotSupportedException {

  CloneExample example1 = new CloneExample("kuluo", 18, new int[]{1, 2, 3});

  CloneExample example2 = example1.clone();

  assertEquals(example1, example2);

  example2.setIntArray(new int[]{4, 5, 6});

  example2.setIntPrimitive(20);

  example2.setStr("kuluoluohaoxiuyi");

  assertEquals(example1, example2);
}

第二个断言会报错

If you write a thread-safe class that implements Cloneable, remember that its clone method must be properly synchronized

Object的clone()方法并不是线程安全的,当多个线程将一个共享的实例进行新对象拷贝时,若某个线程更改了实例的状态,可能会导致其它线程拷贝了错误的新对象,因此需要在clone()方法上添加synchronized关键字

A better approach to object copying is to provide a copy constructor or copy factory

clone()方法的浅拷贝和深拷贝之分增加了我们程序出错的可能性,编写完clone()方法后最好通过测试来进行验证

若不采用clone()方法对实例进行拷贝,我们还可以通过拷贝构造方法和静态拷贝构造方法来实现,这两种方法省去了拷贝时对引用实例是否可变的思考,不会因为final字段的正常使用发生冲突,也不会抛出CloneNotSupportedException异常,甚至不需要在方法内进行强制类型转换

上例代码可以添加如下拷贝构造方法和静态拷贝构造方法

public CloneExample(CloneExample example) { }
public static CloneExample newInstance(CloneExample example) { }

事实上形参列表还可以根据情况有更多的变化,由此相比clone()方法就有颇多的局限

As a rule, copy functionality is best provided by constructors or factories. A notable exception to this rule is arrays, which are best copied with the clone metho.

Effective Java —— 谨慎覆盖clone的更多相关文章

  1. Java - 谨慎覆盖clone

    覆盖clone时需要实现Cloneable接口,Cloneable并没有定义任何方法. 那Cloneable的意义是什么? 如果一个类实现了Clonable,Object的clone方法就可以返回该对 ...

  2. Effective Java 11 Override clone judiciously

    Principles If you override the clone method in a nonfinal class, you should return an object obtaine ...

  3. Java - 谨慎覆盖equals

    平时很难遇到需要覆盖equals的情况. 什么时候不需要覆盖equals? 类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的. 超类已经覆盖了 ...

  4. Effective Java Index

    Hi guys, I am happy to tell you that I am moving to the open source world. And Java is the 1st langu ...

  5. [Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第十---十一条

    第十条 始终覆盖toString() toString的实现可以使类使用起来更加舒适,在执行println等方法时打印出定制信息. 一单实现了自己的toString,指定输出的固定格式,在方法的文档说 ...

  6. Effective Java 之-----谨慎的覆盖clone方法

    1.概述 如果clone方法返回一个由构造器创建的对象,它就得到有错误的类.因此,如果覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone得到的对象.如果类的所有超类都 ...

  7. Effective Java 第三版——13. 谨慎地重写 clone 方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  8. Effective Java 第三版——48. 谨慎使用流并行

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Item 11 谨慎地覆盖Clone

    1.进行浅拷贝时,只是复制原始数据类型的值,则可以通过覆盖Clone方法来达到.另外,在进行浅拷贝的时候,还要注意,成员对象中不应该要有引用类型,如果有引用类型,那么,进行了浅拷贝之后,两个对象将会共 ...

随机推荐

  1. python3中map()函数用法

    python源码解释如下:map(func, *iterables) --> map objectMake an iterator that computes the function usin ...

  2. Seastar 教程(二)

    协程 注意:协程需要 C++20 和支持的编译器.已知 Clang 10 及更高版本可以工作. 使用 Seastar 编写高效异步代码的最简单方法是使用协程.协程没有传统continuation(如下 ...

  3. 国产化之银河麒麟安装达梦数据库DM8

    背景 某个项目需要实现基础软件全部国产化,其中操作系统指定银河麒麟,数据库使用DM8. 虽然在之前的文章中已经成功模拟国产飞腾处理器,但是运行效率不高,所以这里的银河麒麟操作系统还是运行在x64平台上 ...

  4. mplus数据分析:增长模型潜增长模型与增长混合模型再解释

    混合模型,增长混合模型这些问题咨询的同学还是比较多的,今天再次尝试写写它们的区别,希望对大家进一步理解两种做轨迹的方法有帮助. 首先,无论是LCGA还是GMM,它们都是潜增长模型的框框里面的东西: L ...

  5. Jmeter---压力模式

    需求 下面有3个场景,思考一下在jmeter里面如何设计 场景1:有一个项目,500用户同时登录,响应时间能达到多少场景2:考勤打卡,最大吞吐量能达到多少(每秒最大能完成多少笔打卡业务)场景3:银行业 ...

  6. springCould注解和配置以及pom依赖

    SpringCloud注解和配置以及pom依赖说明 在本文中说明了pom依赖可以支持什么功能,以及支持什么注解,引入该依赖可以在application.properties中添加什么配置. 1.Spr ...

  7. Laravel消息队列怎么使用

    使用database驱动做队列 下面是简单使用教程 1. 修改.env文件配置 QUEUE_CONNECTION=sync改成QUEUE_CONNECTION=database 默认的sync是同步队 ...

  8. php 23种设计模型 - 抽象工厂模式

    抽象工厂模式(AbstractFactory) 抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂.该超级工厂又称为其他工厂的工厂.这种类型的设计模式属于创 ...

  9. nginx: [error] open() "/usr/local/nginx/logs/nginx.pid" failed (2: No such file or directory)

    问题场景 服务器重启后,重启nginx时报错nginx: [error] open() "/usr/local/nginx/logs/nginx.pid" failed (2: N ...

  10. ASP.NET Core 6框架揭秘实例演示[21]:如何承载你的后台服务

    借助 .NET提供的服务承载(Hosting)系统,我们可以将一个或者多个长时间运行的后台服务寄宿或者承载我们创建的应用中.任何需要在后台长时间运行的操作都可以定义成标准化的服务并利用该系统来承载,A ...