《Java核心技术 卷I》对象克隆
对象克隆
讨论Cloneable接口,指示一个类提供了一个安全的clone方法。
对象引用建立副本,任何一个变量改变都会影响另一个变量。
如果希望copy一个新对象,初始状态相同,但之后它们各自会有自己不同的状态,这种情况可以使用clone方法。
如果数据域都是数值或其他基本类型,拷贝这些域没有任何问题,但是包含子对象的引用,拷贝域就会得到相同对象的另一个引用,这样一来,原对象和克隆对象仍然会共享一些信息。
以下这种克隆就是浅拷贝,并没有克隆出生日期对象。如果原对象与浅拷贝对象共享子对象不可变,那共享就是安全的。
不过,通常子对象都是可变的,必须重新定义clone方法来建立一个深拷贝,同时克隆出所有子对象。
package corejava;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Objects;
public class EmployeeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Employee e = new Employee("孙悟空", 75000,1987,12,15);
Employee e1 = (Employee) e.clone();
System.out.println(e==e1);
System.out.println(e.equals(e1));
System.out.println(e.hashCode()==e1.hashCode());
System.out.println(e.getHireDay()==e1.getHireDay());
//结果false,true,false,true,总结来说就是浅拷贝,出生日期不该是同一对象。
}
}
class Employee implements Comparable<Employee>,Cloneable {
//实例字段
private String name;
private double salary;
private Date hireDay;
int percent = 10;
//构造器
public Employee(String n,double s,int year,int month, int day) {
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year,month-1,day);
hireDay = calendar.getTime();
}
public Employee() {}
//
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Date getHireDay() {
return hireDay;
}
public void setHireDay(Date hireDay) {
this.hireDay = hireDay;
}
public void raiseSalary(Employee e) {
e.percent = 15;
double raise = salary * e.percent /100;
salary += raise;
}
@Override
public boolean equals(Object otherObject) {
super.equals(otherObject);
// if(this==otherObject) return true;
if(otherObject==null) return false;
if(getClass()!=otherObject.getClass()) return false;
Employee other = (Employee) otherObject;
return this.name.equals(other.name)
&&this.salary==other.salary
&&Objects.equals(this.hireDay, other.hireDay);
}
@Override
public int compareTo(Employee o) {
return Double.compare(getSalary(), o.getSalary());
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
对于每一个类,需要确定:
- 默认的clone方法是否满足要求。
- 是否可以在可变的子对象上调用clone来修补默认的clone方法。
- 是否不该使用clone。
实际上第3个选项是默认选项,如果选择第1项或第2项,类必须:
- 实现Cloneable接口。
- 重新定义clone方法,并指定public访问修饰符。
不实现接口,无法克隆,会报错,可以把克隆接口看成一个标识,当前类所实现的接口数组中包括克隆接口,那么虚拟机执行克隆动作。
注释:Cloneable接口是Java提供的一组标记接口(tagging interface)之一,也称为记号接口(marker interface)。它不包含任何方法,唯一作用就是允许类型查询中使用instanceof,建议你自己的程序中不要使用标记接口。
上面代码改为深拷贝的案例:
//深拷贝
public Employee clone() throws CloneNotSupportedException{
Employee cloned = (Employee) super.clone();
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
所有数组都有clone方法用来建立一个新数组。