首页 > 代码库 > 第11条:谨慎地覆盖clone
第11条:谨慎地覆盖clone
Cloneable接口表明这样的对象时允许克隆的,但这个接口并没有成功达到这个目的,主要是因为它缺少一个clone方法,Object的clone方法是受保护的。如果不借助反射,就不能仅仅因为一个对象实现了Colneable就可以钓鱼clone方法,即使是反射调用也不能保证这个对象一定具有可访问clone方法。
既然Cloneable并没有包含任何方法,那么它到底有什么用呢?它其实觉得了Object中受保护的clone方法实现的行为,如果一个类实现了Cloneable那么Object的clone方法就返回该对象的逐域拷贝,否则会抛出CloneNotSupportedException。但真说接口一种极端非典型用法,不值得提倡。
如果实现Cloneable接口是要对某个类起到作用,类和它的所有超类都必须遵守一个一定协议,言外之意就是无需调用构造器就可以创建对象。
Clone它的通用约定非常弱:
创建和返回该对象的一个拷贝。这个拷贝的精确含义取决于该对象的类。一般含义是,对于任何对象x,表达式x.clone() != x 将会是true,并且,表达式x.clone().getClass() == x.getClass() 将会是true,但这些不是绝对的要求,通常情况下,表达式x.clone().equals(x) 将会是true,这也不是一个绝对的要求,拷贝对象往往是创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。
下面我们看下一个例子:
public class Student implements Cloneable{ String name; int age; public Student(String name,int age){ this.name = name; this.age = age; } public Object clone(){ Object o = null; try{ o = (Student)super.clone();//Object 中的clone()识别出你要复制的是哪个对象 }catch(CloneNotSupportedException e){ System.out.println(e.toString()); } return o; } public static void main(String[] args){ Student s1=new Student("zhangsan",18); Student s2=(Student)s1.clone(); System.out.println("克隆后s2:name="+s2.name+","+"age="+s2.age); s2.name="lisi"; s2.age=20; //修改学生2后,不影响学生1的值。 System.out.println("克隆修改后s1:name="+s1.name+","+"age="+s1.age); System.out.println("克隆修改后s2:name="+s2.name+","+"age="+s2.age); } }
这时候,如果类的每个域包含一个基本类型的值,或者包含一个指向不可变对象的引用,那么被返回的对象则正是所需要的对象,只需要简单地调用super.clone() 而不用做进一步的处理。但是!如果对象中其他对象的引用时,那么只是简单的clone就无法做到完全的克隆了,下面的例子我们就可以体会到
class Professor { String name; int age; Professor(String name,int age){ this.name=name; this.age=age; } } public class Student implements Cloneable{ String name;// 常量对象。 int age; Professor p;// 学生1和学生2的引用值都是一样的。 Student(String name,int age,Professor p){ this.name=name; this.age=age; this.p=p; } public Object clone(){ Student o=null; try{ o=(Student)super.clone(); }catch(CloneNotSupportedException e){ System.out.println(e.toString()); } return o; } public static void main(String[] args){ Professor p=new Professor("wangwu",50); Student s1=new Student("zhangsan",18,p); Student s2=(Student)s1.clone(); System.out.println("克隆后s1:name="+s1.p.name+","+"age="+s1.p.age); System.out.println("克隆后s2:name="+s2.p.name+","+"age="+s2.p.age); s2.p.name="lisi"; s2.p.age=30; System.out.println("克隆后s1:name="+s1.p.name+","+"age="+s1.p.age); System.out.println("克隆后s2:name="+s2.p.name+","+"age="+s2.p.age); } }
从结果上我们可以看出,s2对s1进行克隆时,对s1的属性Professor p并没有进行克隆,导致s1和s2对其引用指向同一个,这会造成s2若改变了值,s1则也被动改变了。那应该如何实现深层次的克隆,即修改s2的教授不会影响s1的教授?其实很简单,只需要对Professor进行修改,如下所示即可
class Professor implements Cloneable{ String name; int age; Professor(String name,int age){ this.name=name; this.age=age; } public Object clone(){ Object o = null; try{ o = super.clone(); }catch(CloneNotSupportedException e){ System.out.println(e.toString()); } return o; } }
修改Professor后,还需要在Student的clone方法中加入一句代码:o.p=(Professor)p.clone();
public Object clone(){ Student o=null; try{ o=(Student)super.clone(); }catch(CloneNotSupportedException e){ System.out.println(e.toString()); } o.p=(Professor)p.clone(); return o; }
看到结果就如我们所希望的那样。因此,在使用clone时,一定要分清需要克隆的对象属性。
第11条:谨慎地覆盖clone