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;
}
}