第11条:谨慎地覆盖clone

Posted 哀&RT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第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的主要内容,如果未能解决你的问题,请参考以下文章

Effective Java目录

谨慎重载clone方法

Effective Java总结的78条

高效Java:clone()方法分析

如何在选项卡 Activity 的片段中使用 onCreateOptionsMenu? (夏洛克动作条)

git clone复制远程代码覆盖非空目录