Cloneable接口表明类提供了安全的clone方法。
首先,说明Object.clone ()方法。
clone是Object超类的protected方法,用户代码不能直接调用此方法。 Object的子类只能通过调用Object超类中受保护的clone方法来克隆自己的对象。 必须将clone重新定义为公共,以便所有方法都可以调用此类实例的clone方法来克隆对象。
clone方法的作用: clone方法对Employee调用对此对象一无所知,所以只能逐域复制。 a .如果对象中的所有数据域都是数字或其他基本类型,则复制这些域没有问题b .但是,如果对象包含对子对象的引用,则复制域是指向同一子对象的另一个域即使这样做,原始对象和克隆对象也会共享信息。
浅复制的影响:如果原始对象和xndss对象共享的子对象不变,则此共享是安全的。 如果子对象属于不可变的类(如String ),则为这种情况。 或者,在对象的生命周期中,子对象始终包含恒定常数,如果没有变更器,则不会变更。 这意味着没有方法生成引用。 这种情况也同样安全。 (LocalDate的域是不变的,如果hireDay是不变的LocaDate类的实例,我们不需要做任何事情。)。
publicclassemployeeimplementscloneable {
私有字符串名称;
私有双销售额;
private Date hireDay;
公共空间(string name,double salary ) )。
{
this.name=name;
this.salary=salary;
hireDay=new Date (;
}
@Override
public Employee clone () throwsclonenotsupportedexception
{
//call object.clone (employee cloned=) employee ) super.clone ); //clone按域复制
//clonemutablefieldscloned.hireday=(date ) hireDay.clone (;
返回克隆;
}
PublicvoidSethiReday(intyear,int month,int day ) )。
{
datenewhireday=newgregoriancalendar (year,month - 1,day ).getTime );
//exampleofinstancefieldmutationhireday.settime (newhireday.gettime );
}
publicvoidraisesalary (双精度) )。
{
double raise=salary * by percent/100;
salary =raise;
}
公共字符串测试(
{
return 'Employee[name=' name ',salary=' salary ',hireDay=' hireDay '];
}
}
公共类主{
publicstaticvoidmain (string [ ] args ) )。
{
employee employee=new employee (' jack1',20000 );
try {
Employee clone=employee.clone (;
employee.raiseSalary(20;
employee.sethi reday (2020,10,22 );
system.out.println (employee.tostring () );
system.out.println (clone.tostring );
} catch (clonenotsupportedexceptione ) )。
e .打印堆栈跟踪(;
}
}
}
/**输出: Employee[name=jack1,salary=24000.0,hireday=thu oct 2200336000336000 CST 2020 ] employee [ na ]
me=jack1,salary=20000.0,hireDay=Sun Oct 13 17:47:26 CST 2019]*/再看一个栗子,hireDay为不可变LocalDate类的实例时(private LocalDate hireDay;),Main.java类代码基本不变(构造函数有改变 -> Employee employee =newEmployee("jack1",20000,2019,10,13);):
public class Employee implements Cloneable {
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name, double salary, int year, int month, int day)
{
this.name = name;
this.salary = salary;
hireDay = LocalDate.of(year, month, day);
}
@Override
public Employee clone() throws CloneNotSupportedException
{
// call Object.clone() Employee cloned = (Employee) super.clone();
// // clone mutable fields// cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
public void setHireDay(int year, int month, int day)
{
// Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();// // Example of instance field mutation// hireDay.setTime(newHireDay.getTime()); hireDay = LocalDate.of(year, month, day);
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public String toString()
{
return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
}
}
对于一个要实现clone方法的类,需要确定:实现Cloneable接口;
重新定义clone方法,并且制定public访问修饰符。
注意:
Cloneable接口的出现与接口的正常实现没有关系。具体讲,它(Cloneable接口)没有指定clone方法,这个方法是从Object类继承的(应该是实现接口的类从Object类继承)。Cloneable接口的作用只是作为一个标记,指示 类设计者 了解克隆过程。对象对于克隆很“偏执”,如果一个对象请求克隆,但没有实现这个接口,就会生成一个受查异常(报错:java.lang.CloneNotSupportedException)。
Cloneable接口是Java提供的一组标记接口(tagging interface)之一。有些程序员也称之为记号接口(marker interface)。注意:Comparable等接口的通常用途是确保一个类实现一个或一组特定的方法。标记接口不包含任何方法,它唯一的作用就是允许在类型查询中使用instanceof:
if (obj instanceof Cloneable) ..
建议自己程序中不要使用标记接口。
即使clone的默认(浅拷贝)实现能够满足要求,还是需要实现Cloneable接口,将clone方法重新定义为public,再调用super.clone()。
class Employee implements Cloneable()
{
// raise visibility level to public, change return type public Employee clone() throws CloneNotSupportedException
{
return (Employee) super.clone();
}
...
}
如果一个类重写了clone方法,但是类没有声明实现接口 implements Cloneable,类会抛出一个CloneNotSupportedException异常。当然,Employee和Date类实现了Cloneable接口,所以不会抛出这个异常。不过编译器不会了解这一点,所以我们声明了这个异常:
public Employee clone() throws CloneNotSupportedException
这样,子类在不支持克隆时选择抛出一个CloneNotSupportedException异常。
clone没有想象中的那么常用,标准库中只有5%的类实现了clone。