【Effective Java】条11:谨慎覆盖clone方法

xiaoxiao2021-02-28  37

Object文档中指出对象需要被clone,则需要实现Cloneable接口。Cloneable接口只是个标记,没有任何方法。

clone约定

对于任何对象x, - x.clone() != x返回为true - x.clone().getClass() == x.getClass()返回为true - x.clone().equals(x)返回为true

但是约定同时指出,这些都不是绝对必须的

重写clone方法

对于类中只有原始类型或者不可变的变量,直接调用父类的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(); } } 对于类中的属性为引用类型的,除了调用父类的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(); } } }

为什么要慎用

从重写clone方法章节中可以知道,默认的clone方法是浅拷贝,需要深拷贝的话需要重写clone方法对属性进行重赋值,使代码看起来很繁琐接口表示的是客户可以调用的方法,但是Cloneable接口没有任何方法,仅仅起标记作用,且子类(需要深拷贝)修改了父类的默认拷贝行为

所以一般在开发中,很少使用clone方法。另在《阿里巴巴Java开发手册》中,有条建议:【推荐】慎用Object的clone方法来拷贝对象。因此在日常开发中,我们应该尽量避免采用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; } } 拷贝工厂 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; } }
转载请注明原文地址: https://www.6miu.com/read-2613812.html

最新回复(0)