Effective Java —— 谨慎覆盖clone

本文参考

本篇文章参考自《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();
    /*
     *
如果intArrayfinal,则此处无法再进行拷贝赋值
     *
因为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.

posted @ 2020-05-12 11:39  咕~咕咕  阅读(459)  评论(0编辑  收藏  举报