【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;
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值