Java 中内置了一些很有用的接口, Clonable 就是其中之一。
本文会对Clonable接口进行讨论,这个接口指示一个类提供了一个安全的clone方法,调用这个方法可以创建一个对象的 “拷贝”。
clone方法是Object的一个protected方法,这说明你的代码不能直接调用clone。只有…类可以克隆…对象。必须重新定义clone为public才能允许所有方法克隆对象
( •̀ ω •́ )✧这样子的限制是有原因的:
想想看Object如何实现clone。他对你要克隆的对象一无所知,所以只能逐个域的进行拷贝(让人家干活还不告诉人家怎么干?)。如果该对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题。但如果对象包含子对象的引用,拷贝后就会得到相同子对象的另一个引用,这样一来原对象和克隆的对象仍然会共享信息,此时发生的就是浅拷贝。而默认的克隆操作就是浅拷贝。
但是,即便clone的默认(浅拷贝)实现能够满足要求,还是需要实现Cloneable接口,将clone重新定义成public,再调用super.clone().
Eg:
class Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException
{
return (Employee) super.clone();
}
..........
}
与Object.clone提供的浅拷贝相比,上面的clone方法没有为他增加任何功能,只是让这个方法变成公开的。想要建立深拷贝还要做更多工作,克隆对象中的可变实例域。
(注意:使用clone方法必须实现Cloneable接口,这个接口只是作为标记(叫做标记接口),具体来说,他没有指定clone方法,这个方法是从Object类继承的。但是如果一个对象请求克隆但是没有实现Cloneable接口,就会生成一个异常)
下面将对深拷贝的实现给出代码举例:
//main
package clone;
public class CloneTest {
public static void main(String[] args) {
try {
Employee original = new Employee("Ethan",50000);
original.setHireDay(2000,1,1);
Employee copy=original.clone();
copy.raiseSalary(10);
copy.setHireDay(2022,6,3);
System.out.println(original);
System.out.println(copy);
}
catch (CloneNotSupportedException e){
e.printStackTrace();
}
}
}
//Employee类
package clone;
import java.util.Date;
import java.util.GregorianCalendar;
public class Employee implements Cloneable{
private String name;
private double salary;
private Date hireDay;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
@Override
public Employee clone() throws CloneNotSupportedException {
Employee cloned=(Employee) super.clone();
cloned.hireDay=(Date) hireDay;
return cloned;
}
public void setHireDay(int year,int month,int day){
Date newHireDay = new GregorianCalendar(year,month-1,day).getTime();
hireDay.setTime(newHireDay.getTime());
}
public void raiseSalary(double byPercent){
double raise = salary * byPercent / 100;
salary += raise;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
", hireDay=" + hireDay +
'}';
}
}
上述程序克隆了Employee类的一个实例,然后调用两个更改器方法。raiseSalary方法会改变salary域的值,而setHireDay方法改变hireDay域的状态。这两个更改其方法都不会影响原来的对象,因为clone定义为建立一个深拷贝
下面我们将对程序的关键步骤运行时内存发生的事情进行图解,提便于更好的理解clone:
Employee copy=original.clone();
copy.raiseSalary(10);
copy.setHireDay(2022,6,3);