Java 的 clone
方法用于创建并返回一个对象的拷贝。
clone()
方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存,相对应的深拷贝则会连引用的对象也重新创建。
方法签名
protected native Object clone() throws CloneNotSupportedException;
clone()
是 Object 类的方法,且被定义为 protected。
由于 Object 本身没有实现 Cloneable 接口,所以不重写 clone 方法并且进行调用的话会发生 CloneNotSupportedException
异常。
方法示例
首先,创建一个学生实体对象,实现 Cloneable 接口,并重写 clone() 方法。
需要注意的是,这里的 clone() 方法是固定写法,可以直接由 IDE 自动生成。
public class Student implements Cloneable{
private String name;
private Integer age;
// setters、getters
@Override
public Student clone() {
try {
Student clone = (Student) super.clone();
// TODO: copy mutable state here, so the clone can't change the internals of the original
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
first Student 是正常创建的对象, sencond Student 是从 first 克隆而来。
public static void main(String[] args) {
Student first = new Student();
first.setAge(6);
first.setName("Tom");
System.out.println("First student " + first);
Student second = first.clone();
System.out.println("Second student " + second);
// 修改原始对象内容
first.setName("Jerry");
System.out.println("First student " + first);
System.out.println("Second student " + second);
}
从结果可以看出来,修改 first 的内容,不影响 student,因此他们是不同的对象。
Second student Student(name=Tom, age=6)
First student Student(name=Jerry, age=6)
Second student Student(name=Tom, age=6)
深浅克隆
Java 中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆 ShallowClone 和深克隆 DeepClone 的主要区别在于是否支持引用类型的成员变量的复制。
在 Student 中加入一个对象引用 Address address,其他不变。
public class Student implements Cloneable{
private String name;
private Integer age;
private Address address;
@Override
public Student clone() {
try {
Student clone = (Student) super.clone();
// TODO: copy mutable state here, so the clone can't change the internals of the original
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
Student 中引用的是 Address 对象,就是一个普通的 Java 对象。
public class Address {
/**
* 住址编码
*/
private String code;
/**
* 门牌号
*/
private String houseNumber;
}
还是之前的例子,在 Student 中添加 Address 引用。
private static void referClone(){
Address address = new Address();
address.setCode("10101");
address.setHouseNumber("2号楼302");
Student first = new Student();
first.setAge(6);
first.setName("Tom");
first.setAddress(address);
System.out.println("First student " + first);
Student second = first.clone();
System.out.println("Second student " + second);
// 修改原始对象内容
first.getAddress().setHouseNumber("5号楼101");
System.out.println("First student " + first);
System.out.println("Second student " + second);
}
不同的是,这次对原始 Student 中引用对象的 address 某个属性值修改。
Second student Student(name=Tom, age=6, address=Address(code=10101, houseNumber=2号楼302))
First student Student(name=Tom, age=6, address=Address(code=10101, houseNumber=5号楼101))
Second student Student(name=Tom, age=6, address=Address(code=10101, houseNumber=5号楼101))
可以看到修改 first 对象,second 对象也跟着变了。原因是浅复制只是复制了 address 变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
浅克隆转深克隆
在对 Student 克隆时,Address 并没有被成功克隆,有没有办法让 Address 也被克隆呢?
既然 Address 没有复制,我们可以尝试对 Address 采取 Student 一样的方式,手动处理。
Address 也实现 Cloneable 类,并重写 clone()
方法。
public class Address implements Cloneable{
/**
* 住址编码
*/
private String code;
/**
* 门牌号
*/
private String houseNumber;
@Override
public Address clone() {
try {
Address clone = (Address) super.clone();
// TODO: copy mutable state here, so the clone can't change the internals of the original
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
此外,在重写 Student 的 clone() 方法时,指定 address 的复制方法。
public class Student implements Cloneable{
private String name;
private Integer age;
private Address address;
@Override
public Student clone() {
try {
Student clone = (Student) super.clone();
// TODO: copy mutable state here, so the clone can't change the internals of the original
clone.address = address.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
注意,在 Student 的 clone() 方法中,多了一行 clone.address = address.clone();
还是 referClone()
方法,执行后,可以发现 second 并没有跟着 first 的内容改变而改变。
Second student Student(name=Tom, age=6, address=Address(code=10101, houseNumber=2号楼302))
First student Student(name=Tom, age=6, address=Address(code=10101, houseNumber=5号楼101))
Second student Student(name=Tom, age=6, address=Address(code=10101, houseNumber=2号楼302))
克隆嵌套问题
上面对 Address 克隆的方法,在实际编码中,并不常见。如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。
在实际开发中,深克隆往往才用序列化的方式处理,也就是实现 Serializable 接口。
也就是为什么,我们常常会看到如下的代码:
public class Teacher implements Serializable {
private String name;
private Integer age;
}
实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。