【Effective Java】條11:謹慎覆蓋clone方法

Object文檔中指出對象需要被clone,則需要實現Cloneable接口。Cloneable接口只是個標記,沒有任何方法。

clone約定

對於任何對象x
- x.clone() != x返回爲true
- x.clone().getClass() == x.getClass()返回爲true
- x.clone().equals(x)返回爲true

但是約定同時指出,這些都不是絕對必須的

重寫clone方法

  1. 對於類中只有原始類型或者不可變的變量,直接調用父類的clone方法即可
public class PhoneNumber implements Cloneable {

  private short areaCode;
  private short prefix;
  private short lineNumber;

  public PhoneNumber(int areaCode, int prefix, int lineNumber) {
    rangeCheck(areaCode, 999, "area code");
    rangeCheck(prefix, 999, "prefix");
    rangeCheck(lineNumber, 9999, "line number");

    this.areaCode = (short) areaCode;
    this.prefix = (short) prefix;
    this.lineNumber = (short) lineNumber;
  }

  private static void rangeCheck(int arg, int max, String name) {
    if (arg < 0 || arg > max) {
      throw new IllegalArgumentException(name + ": " + arg);
    }
  }

  @Override
  public boolean equals(Object o) {
    //==判斷
    if (o == this) {
      return true;
    }

    //instanceof判斷
    if (!(o instanceof PhoneNumber)) {
      return false;
    }

    //各屬性判斷
    PhoneNumber pn = (PhoneNumber) o;
    return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
  }

  @Override
  protected PhoneNumber clone() throws CloneNotSupportedException {
    return (PhoneNumber) super.clone();
  }
}
  1. 對於類中的屬性爲引用類型的,除了調用父類的clone方法之外,引用類型的屬性也需要重新賦值
public class User implements Cloneable {
  private String name;
  private int sex;
  private String phone;
  private Address address;

  public User() {
  }

  public User(String name, int sex, String phone, Address address) {
    this.name = name;
    this.sex = sex;
    this.phone = phone;
    this.address = address;
  }

  @Override
  protected Object clone() throws CloneNotSupportedException {
    User user = (User) super.clone();
    user.address = address.clone();

    return user;
  }

  class Address implements Cloneable {
    private String city;
    private String area;
    private String road;

    public Address(String city, String area, String road) {
      this.city = city;
      this.area = area;
      this.road = road;
    }

    @Override
    protected Address clone() throws CloneNotSupportedException {
      return (Address)super.clone();
    }
  }
}

爲什麼要慎用

  1. 重寫clone方法章節中可以知道,默認的clone方法是淺拷貝,需要深拷貝的話需要重寫clone方法對屬性進行重賦值,使代碼看起來很繁瑣
  2. 接口表示的是客戶可以調用的方法,但是Cloneable接口沒有任何方法,僅僅起標記作用,且子類(需要深拷貝)修改了父類的默認拷貝行爲

所以一般在開發中,很少使用clone方法。另在《阿里巴巴Java開發手冊》中,有條建議:【推薦】慎用Objectclone方法來拷貝對象。因此在日常開發中,我們應該儘量避免採用clone的方法

替換方法

常替換的方法有:
1. 拷貝構造器

public class PhoneNumber {

  private short areaCode;
  private short prefix;
  private short lineNumber;

  public PhoneNumber(int areaCode, int prefix, int lineNumber) {
    rangeCheck(areaCode, 999, "area code");
    rangeCheck(prefix, 999, "prefix");
    rangeCheck(lineNumber, 9999, "line number");

    this.areaCode = (short) areaCode;
    this.prefix = (short) prefix;
    this.lineNumber = (short) lineNumber;
  }

  //拷貝構造器
  public PhoneNumber(PhoneNumber phoneNumber) {
    this.areaCode = phoneNumber.getAreaCode();
    this.prefix = phoneNumber.getPrefix();
    this.lineNumber = phoneNumber.getLineNumber();
  }

  private static void rangeCheck(int arg, int max, String name) {
    if (arg < 0 || arg > max) {
      throw new IllegalArgumentException(name + ": " + arg);
    }
  }

  public short getAreaCode() {
    return areaCode;
  }

  public void setAreaCode(short areaCode) {
    this.areaCode = areaCode;
  }

  public short getPrefix() {
    return prefix;
  }

  public void setPrefix(short prefix) {
    this.prefix = prefix;
  }

  public short getLineNumber() {
    return lineNumber;
  }

  public void setLineNumber(short lineNumber) {
    this.lineNumber = lineNumber;
  }
}
  1. 拷貝工廠
public class PhoneNumber {

  private short areaCode;
  private short prefix;
  private short lineNumber;

  public PhoneNumber(int areaCode, int prefix, int lineNumber) {
    rangeCheck(areaCode, 999, "area code");
    rangeCheck(prefix, 999, "prefix");
    rangeCheck(lineNumber, 9999, "line number");

    this.areaCode = (short) areaCode;
    this.prefix = (short) prefix;
    this.lineNumber = (short) lineNumber;
  }

  //拷貝工廠方法
  public static PhoneNumber newInstance(PhoneNumber phoneNumber) {
    return new PhoneNumber(phoneNumber.getAreaCode(), phoneNumber.getPrefix(), phoneNumber.getLineNumber());
  }

  private static void rangeCheck(int arg, int max, String name) {
    if (arg < 0 || arg > max) {
      throw new IllegalArgumentException(name + ": " + arg);
    }
  }

  public short getAreaCode() {
    return areaCode;
  }

  public void setAreaCode(short areaCode) {
    this.areaCode = areaCode;
  }

  public short getPrefix() {
    return prefix;
  }

  public void setPrefix(short prefix) {
    this.prefix = prefix;
  }

  public short getLineNumber() {
    return lineNumber;
  }

  public void setLineNumber(short lineNumber) {
    this.lineNumber = lineNumber;
  }
}
發佈了98 篇原創文章 · 獲贊 180 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章