Effective Java 【对于所有对象都通用的方法】第13条 谨慎地覆盖clone_为继承设计类有两种选择,但无论选择其中的-程序员宅基地

技术标签: java  Effective Java  设计模式  

谨慎地覆盖clone

Cloneable接口地目的是作为对象的一个mixin接口(详见第20条),表示这样的对象允许克隆(clone)。遗憾的是,他并没有成功地达到这个目的。它的主要缺陷在于缺少一个clone方法,而Object的clone方法是受保护的。如果不借助反射方法。即使是反射调用也可能失败,因为不能保证对象一定具有可访问的clone方法。尽管存在缺陷,这项设施仍然被广泛使用,因此值得我们进一步了解。本条目将告诉你如何实现一个行为良好的clone方法,并讨论何时适合这样做,同时也简单的讨论了其他的可替代做法

Cloneable接口并没有包含任何方法,那么它到底有什么作用呢?

他决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法就会返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedExcepton异常。这是接口的一种极端极端非典型的用法,也不值得效仿。通常情况下,实现接口是为了表示类可以为它的客户做些什么。然而,对于Cloneable接口,他改变了超类中受保护的方法的行为。

简而言之:Cloneable接口标志了这个类可以调用自身或者超类中的clone()方法,如果没有申明实现该接口则会抛出CloneNotSupportedExcepton异常。

Object类中的clone()方法

Object最为所有类的超类,它的clone()方法这样定义:创建并返回一个对象的拷贝。

clone 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存,相对应的深拷贝则会连引用的对象也重新创建。

也就是说对于值类型clone()方法会直接copy一份新的到克隆对象中,但是对于引用类型,两个克隆对象中某一对象的引用还是指向内存中同一个地址。这往往会代码许多灾难性的后果。

所以如果要利用clone方法进行克隆出良好的对象的话,需要讲克隆对象中的引用类型逐一克隆

如何重写好一个clone()方法

其实要点就在于需要对克隆对象域中的引用域进行逐一克隆,下面给出几个关键注意点:

1.对于数组类型我可以采用clone()方法的递归

数组类:

public class Stack {
    

    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
    
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
    
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
    
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];

        elements[size] = null; // Eliminate obsolete reference
        return result;
    }

    // Ensure space for at least one more element.
    private void ensureCapacity() {
    
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

clone()方法:

public Stack clone() {
    
    try {
    
        Stack result = (Stack) super.clone();
        result.elements = elements.clone();
        return result;
    } catch (CloneNotSupportedException e) {
    
        throw new AssertionError();
    }
}

实际上,clone方法就是另一个构造器,必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中地约束条件。

还要注意如果elements域是final的,上述方案就不能正常工作,因为clone方法是被禁止给final域赋新值的。这是个根本问题:就像序列化一样,Cloneable架构与引用可变对象的final域的正常用法是不相兼容的。除非在原始对象和克隆对象之间可以安全地共享此可变对象。为了使类成为可克隆地,可能有必要从某些域中去掉final修饰符。

2.如果对象是非数组,建议提供拷贝构造器(copy constructor)或者拷贝工厂(copy factory)

clone方法对于数组来说其实可以简单递归调用clone达到克隆的效果。
但如果对象在复杂一点,比如涉及到散列表,eg:HashMap,此时clone方法会变得十分复杂。

其实clone方法无法是构造器的另一种方式,参考这种行为我们完全可以提供一个专门拷贝对象的构造器或者工厂方法,只需要传入被克隆的兑现即可。
eg:

public Yum(Yum yum){
    ......}

拷贝构造器的做法,及其静态工厂方法的变形,都比Cloneable/clone方法具有更多的优势:

  1. 它们不依赖于某一种很有风险的、语言之外的创建对象机制;
  2. 它们不要求遵守尚未制定好的文档规范;
  3. 它们不会与final域的正常使用发生冲突;
  4. 它们不会抛出不必要的受检异常;
  5. 它们不需要进行类型转换。

甚至,拷贝构造器或者拷贝工厂可以带一个参数,参数类型是该类所实现的接口。例如,按照惯例所有通用集合实现都提供一个拷贝构造器,其参数类型是Collection或者Map接口。基于接口的拷贝构造器和拷贝工厂(更准确的叫法应该是转换构造器)和转换工厂,允许客户选择拷贝的实现类型,而不是强迫客户接收原始的实现类型。例如,假设你有一个HashSet:s,并且希望把它拷贝成一个TreeSet。clone方法无法提供这样的功能,但是转换构造器很容易实现:new TreeSet<>(s)。

3.如果为线程安全的类重写clone()方法

为了保证一个对象克隆出来的两个对象是完全不一样的,我们需要给clone()方法加上同步才行。
eg:

public Object synchronized clone(){
    
....
}

4.如果为需要被继承的类重写clone()方法

为继承(详见第19条)设计类有两种选择,但是无论选择其中的哪一种方法,这个类都不应该实现Cloneable接口。你可以选择模拟Object的行为:实现一个功能适当的受保护的clone方法,他应该被声明抛出CloneNotSupportedException异常。这样可以使子类具有实现或者不实现Cloneable接口的自由,就仿佛它们直接扩展了Object一样,或者,也可以选择不去实现一个有效的clone方法,并防止子类去实现它,只需要提供下列退化了的clone实现即可:

// clone method for extendable class not supporting Cloneable
@Override
protected final Object clone() throws CloneNotSupportedException {
    
    throw new CloneNotSupportedException();
}

总结

所有的问题都是与Cloneable接口有关,新的接口就不应该扩展这个接口,新的可扩展的类也不应该实现这个接口。虽然final类实现Cloneable接口没有太大危害,这个应该被视同性能优化,留到少数必要的情况下才使用(详见第67条),总之,复制功能最好由构造器或者工厂提供。这条规则最绝对的例外是数组,最好利用clone方法复制数组。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_38941937/article/details/115533412

智能推荐

sphinx 简介以及安装 以及php拓展开启-程序员宅基地

文章浏览阅读117次。一 sphinx 简介 在 使用mysql数据库过程中,如果想实现全文检索的优化,可以使用mysql自带全文索引,但是不支持中文。。关于sphinx的安装网上很多教程写的都 不错比如:http://www.coreseek.cn/products-install/。这里就不再说明安装方法了。有兴趣的可以自己参考。 MySQL在高并发连接、数据库记录数较多的情况下,S..._test -z "/usr/local/sphinx/lib" || mkdir -p -- "/usr/local/sphinx/lib

硬盘是计算机中不能缺少的部件,计算机基础综合测试题(附答案)-程序员宅基地

文章浏览阅读622次。我们学校的课件共5页,第1页 1历 年 试 题 精 选一、 选择题(单选)1. 现代计算机之所以能自动地连续进行数据处理,主要是因为__C_____。A 、采用了开关电路B 、采用了半导体器件C 、具有存储程序的功能D 、采用了二进制2. 一个完整的计算机系统通常应包括____C___。A 、系统软件和应用软件B 、计算机及其外部设备C 、硬件系统和软件系统D 、系统硬件和系统软件3. 微型计算机..._计算机内存,硬盘的题目

如何游刃有余地应聘Go语言开发工程师-程序员宅基地

文章浏览阅读423次。最近很多人都看到了郝林通过大病筹款平台轻松筹发起筹款的事情,小编受作者所托,感谢开发者们在他生病期间的经济与精神上的支持和鼓励。目前郝林病情暂时稳定,并已决定把筹集的 1..._go语言开发 招聘

jbpm4.4集成mysql_JBPM4.4安装(Tomcat-MySQL)-程序员宅基地

文章浏览阅读77次。一、下载相关的工具软件JBPM4.4下载(http://sourceforge.net/projects/jbpm)Apache-tomcat-6.0.35下载(http://tomcat.apache.org/download-60.cgi)MySQL下载(http://www.mysql.com/downloads/)Eclipse-java-galileo-SR2-win32下载(http:..._jbpm-4.4/install/src/db/create/jbpm.mysql.create.sql在哪儿

linux能在pc上运行吗,技术|在Windows 8 PC上安装Linux的四种方法-程序员宅基地

文章浏览阅读156次。之前有消息称Windows 8的安全机制将会阻碍其他操作系统的启动。现在,很多想在Mac上运行Linux的人使用兼容支持模块CSM,提供Mac上BIOS的仿真。这种方式很麻烦,运行得不好,在Secure Boot Windows 8 PC上可能会更糟糕。同时随着微软Windows 8 发布的日子越来越近,Windows 8 中的 UEFI 功能和效应也会慢慢显示出来,所以在 PC 上安装 Linu..._linux能运行在pc机上吗

Git使用心得_echo "# oneupyenova" >> readme.md git init git add-程序员宅基地

文章浏览阅读170次。Github使用记录最近对github的一些使用心得清除git工程的git信息rm -rf .git本地创建git项目并push到远程echo "# Readme" >> README.mdgit initgit add README.mdgit commit -m "first commit"git remote add origin [email protected]:addressgit push -u origin masterpush一个已经存在的库git rem_echo "# oneupyenova" >> readme.md git init git add readme.md git commit -m

随便推点

帆软报表各种情形下引入js_帆软使用js-程序员宅基地

文章浏览阅读804次。3.EncryptionComponent.KEY——$HOST/v10/encryption/page 平台切换国密的时候才可能会用到,主要用于提示异常。8.MigrationComponent.KEY——$HOST/v10/migration/page FineDb迁移数据时的进度页面,在插件中用处不大。12.WorkflowComponent.KEY——$HOST/workflow/authority 单独访问多级上报权限控制页面时生效。被依赖组件——前端页面。被依赖组件——前端页面。_帆软使用js

高可用 | Xenon 实现 MySQL 高可用架构 部署篇_xaeon mysql-程序员宅基地

文章浏览阅读906次。在《高可用 | Xenon:后 MHA 时代的选择》一文中,我们对 Xenon 的实现原理、应用场景等做了简要介绍。文章发布后,社区小伙伴都在咨询 Xenon 如何与 MySQL 配合使用?本文来自知数堂投稿,是一篇基于 Xenon 架构原理,部署 一主两从 架构的 MySQL 高可用集群的实操文档。Xenon 架构图 环境信息:Redhat 7MySQL 5.7Xenon 1.0.7XtraBackup 24*另:Xenon 支持 MySQL 5.6/5.7/8.0 内核,本文以_xaeon mysql

移植Linux内核到阿尔法开发板(一)编译NXP官方Linux内核_nxp linux-程序员宅基地

文章浏览阅读944次,点赞7次,收藏20次。移植Linux内核到阿尔法开发板(一)编译NXP官方Linux内核_nxp linux

关于HashMap的加载因子相关理解_hashmap加载因子-程序员宅基地

文章浏览阅读399次。HashMap在JDK1.7是以数组加链表的形式组成,JDK1.8后新增了红黑树结构,当链表大于8并且容量大于64时,链表结构会转成红黑树结构。JDK1.8 之所以会加入红黑树是因为当链表过长是会严重影响HashMap的性能,而红黑树具有快速增删改查的特点。关于加载因子加载因子也叫作扩容因子,用来判断什么时候进行扩容,假设加载因子为0.75,HashMap的初始容量为16,当HashMap中有16 * 0.75 = 12个容量时,HashMap就会进行扩容。如果加载因子越大,扩容发生的频率就会比较低_hashmap加载因子

python一二三【warning模块使用】_import warnings-程序员宅基地

文章浏览阅读1.3w次,点赞2次,收藏10次。warning模块使用目的和exception异常要求用户立刻进行处理不同,warning通常用于提示用户一些错误或者过时的用法。casescrapy源码中用到了继承了Warning类创建了一个提醒对象ScrapyDeprecationWarning,用于提醒过时的用户操作,在新版本可能会直接去除支持。用户感知warningspython参数控制warning输出 ..._import warnings

ssm常用的注解_ssm框架注解-程序员宅基地

文章浏览阅读2.3k次。@RestController此注解有两个目的。首先他是一个类似于@controller和@Service的构造型注解,能够让类被组件扫描功能发现。但是,与REST最相关在于@RestController会告诉Spring,控制器中所有的处理器方法的返回值都要直接写入响应体中,而不是将值放到模型中并传递给一个视图以便于渲染。作为替代方案就是@Controller加上@Response。@RestControllerpublic class Controller { }@ApiVersi_ssm框架注解

推荐文章

热门文章

相关标签