第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时,一定要分清需要克隆的对象属性。

时间: 2024-12-18 04:23:11

第11条:谨慎地覆盖clone的相关文章

第十一条:谨慎的覆盖clone()方法

一个类要想实现克隆,需要实现Cloneable接口,表明这个类的对象具有克隆的功能. Cloneable接口是一个mixin接口,它里面并没有任何的抽象方法,类似的接口有Serializable接口,表明该类的对象可以序列化. 首先应该明确通过一个对象克隆出另一个对象的概念:通过一个对象克隆出另一个对象,简称对象1和对象2,这两个对象就成为两个独立的对象, 那么对对象1做任何的修改,对象2应该不受影响:对对象2做任何的修改的,对象1就不应该受影响.这样就要求对象1与对象2的实例域各自是独立的.

Effective Java - 谨慎覆盖clone

覆盖clone时需要实现Cloneable接口,Cloneable并没有定义任何方法. 那Cloneable的意义是什么? 如果一个类实现了Clonable,Object的clone方法就可以返回该对象的逐域拷贝,否则会抛出CloneNotSupportedException. 通常,实现接口是为了表明类的行为. 而Cloneable接口改变了超类中protected方法的行为. 这是种非典型用法,不值得仿效. 好了,既然覆盖了clone方法,我们需要遵守一些约定: x.clone() != x

Effective Java 第三版——13. 谨慎地重写 clone 方法

Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化. 在这里第一时间翻译成中文版.供大家学习分享之用. 13. 谨慎地重写 clone 方法 Cloneable接口的目的是作为一个mixin接口(条目 20),公布这样的类允许克隆.不幸的是,它没有达到这个目的.它的主要缺点是

提高程序员项目设计水平的11条建议

1.分析清楚你要面对的项目. 这是进行所有工作的第一步,如果你在这个地方都犯错了,那么后续所做的一切努力都是白费.比如,你要完成图书管理系统,那么你就应该明白这个系统主要是用于解决图书管理的问题. 2.考虑项目设计的方法. 在这个阶段,你要根据这个项目的特点考虑设计的工具和语言.比如,对于图书管理系统,它需要封装性好,这样有利于管理和维护.而这个系统开发周期较短,功能较简单.所以,根据其特点,选择Java作为开发语言会是一个不错的选择. 3.收集完整的需求. 花时间整理清楚你的用户群的基本需求有

我所遵守的11条数据库设计准则

前言:作者Shivprasad koirala,前微软ASP/ASP.NET的MVC工程师,现于印度任CEO职位.学生初次翻译,如有不妥,不吝赐教. 简介 在你开始阅读这篇文章之前,我要先告诉你我不算是数据库设计方面的什么大师.下面的11条准则,是我从项目.从我自身的经验和我自己的理解和学习中得来的.我个人认为在数据库设计方面运用这些准则能使我受益匪浅.我也欢迎任何批评与指点. 我之所以要写这么一篇详尽的文章,是因为,许多开发者设计数据库的时候十分信奉"三个范式"(译者注:而没有结合实

成为一个优秀程序员的11条小贴士

我是一个充满了激情的程序员,所以我觉得我很了解程序员.在这个领域耕耘了这么多年,我和许多非常聪慧的人们接触,他们编写了具有创意的代码,但是当其他人来维护这些代码的时候,他们就很抓狂了! 能够激励程序员的最重要的一点就是他们的激情.我们对于编写良好的程序富有激情,所以我们整合了一个有11条小贴士的清单来帮助您成为一个优秀的程序员.无论您是刚开始学习程序设计还是一个有经验的开发者,有一些东西是您在参考手册上找不到的.备注:我们不说是伟大的.但,绝对是很好的,也就是说好到足够让一些程序员依赖或者使用它

陈松松:经验告诉我们,做视频营销这11条标准一条也不能缺

每个视频,都是你的金牌业务员 这是我写的第55篇视频营销原创文章 与其搜索十年,不如花一年的时间学习,去赚9年的高薪! 这11条标准,是我在操作视频营销的时候谨记的11个规则,希望在你操作视频营销的时候作为你参考的标准. 标准01:每天至少原创一个优质视频 既然做视频营销,就要下定决心,要么就彻底放弃,不做三天打鱼,两天塞网的事情,伤神又费力.一年365天,去掉节假日,至少持续更新360天! 标准02:发布视频到所有视频平台 做一个视频不容易,来一个客户更不容易,把你制作的每一个视频发布到所有的

linux基础篇-linux必备11条 简述

1>, ls:list directory contents 列出目录内容 -l:长格式 文件类型 -:普遍文件 d:目录文件 b:块设备(block) c:字符文件(character) l:符合链接文件(symbolic link file) p:命令管道文件(socket) [[email protected] ~]# ls -ld /tmp drwxrwxrwt. 5 root root 4096 11月 10 10:36 /tmp 文件权限+属主(owner)+属组(group)+文件

linq to sql 怎么查询前 11 条数据

(from 新表 in db.books where 新表.bookid < 400 select 新表).Take(11); storeDB.Albums.OrderByDescending(a => a.OrderDetails.Count()).Take(count).ToList();数据上下文.Albums数据表.倒序排序(条件为按照各数据关联的OrderDetails数据表中数据的条数).拿记录(count条).立即执行转为list