博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第11条:谨慎地覆盖clone
阅读量:7001 次
发布时间:2019-06-27

本文共 3275 字,大约阅读时间需要 10 分钟。

Clone提供一种语言之外的机制:无需调用构造器就可以创建对象。

它的通用约定非常弱:

创建和返回该对象的一个拷贝。这个拷贝的精确含义取决于该对象的类。一般含义是,对于任何对象x,表达式x.clone() != x 将会是true,并且,表达式x.clone().getClass() == x.getClass() 将会是true,但这些不是绝对的要求,通常情况下,表达式 x.clone().equals(x) 将会是true,这也不是一个绝对的要求,拷贝对象往往是创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。

 

如果类的每个域包含一个基本类型的值,或者包含一个指向不可变对象的引用,那么被返回的对象则正是所需要的对象,如PhoneNumber类:

public class PhoneNumber implements Cloneable{    private final int areaCode;    private final int prefix;    private final int lineNumber;        public PhoneNumber(int areaCode, int prefix, int lineNumber) {        rangeCheck(areaCode, 999, "area code");        rangeCheck(prefix, 999, "prefix");        rangeCheck(lineNumber, 9999, "line number");        this.areaCode = areaCode;        this.prefix = prefix;        this.lineNumber = lineNumber;    }        private static void rangeCheck(int arg, int max, String name) {        if(arg < 0 || arg > max) {            throw new IllegalArgumentException(name + ": " + arg);        }    }        @Override    public boolean equals(Object o) {        if(o == this)            return true;        if(!(o instanceof PhoneNumber))            return false;        PhoneNumber pn = (PhoneNumber)o;        return pn.lineNumber == lineNumber                && pn.prefix == prefix                && pn.areaCode == areaCode;    }        @Override    public PhoneNumber clone() {        try {            return (PhoneNumber) super.clone();        } catch(CloneNotSupportedException e) {            throw new AssertionError();        }    }}

只需要简单地调用super.clone() 而不用做进一步的处理。

 

如果对象中包含的域引用了可变的对象:

 

public class Stack {    private Object[] elements;    private int size = 0;    private static final int DEFAULT_INITAL_CAPACITY = 16;        public Stack() {        elements = new Object[DEFAULT_INITAL_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;        return result;    }        private void ensureCapacity() {        if(elements.length == size)            elements = Arrays.copyOf(elements, 2 * size + 1);    }}

如果把这个类做成是可克隆的。如果它的clone方法仅仅返回super.clone(),这样得到的Stack实例,在size域有正确的值,但它的elements域将引用与原始Stack实例相同的数组。

为了使Stack类中的clone方法正常地工作,必须拷贝栈的内部信息:

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

如果elements域是final的,上面的clone方法无法正常工作,因为clone方法被禁止给elements赋新值。

 

另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。拷贝构造器只是一个构造器,它唯一的参数类型是包含该构造器的类,例如 public Yum(Yum yun);

拷贝工厂是类似于拷贝构造器的静态工厂:public static Yum newInstance(Yum yum);

它们不依赖于有风险的、语言之外的对象创建机制,也不要求遵守尚未制定好文档的规范,不会与fianl域的正常使用发生冲突,不抛出不必要的受检异常,不需要进行类型转换。

拷贝构造器或者拷贝工厂可以带一个参数,参数类型是通过该类实现的接口,例如所有通用集合实现都提供一个拷贝构造器,它的参数类型是Collection或者Map。基于接口的拷贝构造器和拷贝工厂允许客户选择拷贝的实现类型,例如假设有一个HashSet s,希望把它拷贝成一个TreeSet,clone方法无法提供这样的功能,但用转换构造器实现:new TreeSet(s)。

 

cloneable的问题导致我们不应该扩展这个接口,为了继承而设计的类也不应该实现这个接口,由于它具有这么多缺点,专家级的程序员从来不去覆盖clone方法, 也从来不去调用它,除非拷贝数组。

对于一个专门为继承而设计的类,如果未能提供行为良好的受保护clone方法,它的子类就不可能实现Cloneable接口。

转载于:https://www.cnblogs.com/13jhzeng/p/5650128.html

你可能感兴趣的文章
Spark入门实战系列--5.Hive(上)--Hive介绍及部署
查看>>
tomcat设置web根目录
查看>>
CF 444B(DZY Loves FFT-时间复杂度)
查看>>
OCP-1Z0-051-名称解析-文章12称号
查看>>
UVALive 4225 Prime Bases 贪心
查看>>
Oracle B-tree、位图、全文索引三大索引性能比较及优缺点汇总
查看>>
[.net 面向对象程序设计进阶] (20) 反射(Reflection)(上)利用反射技术实现动态编程...
查看>>
【转】java中float与byte[]的互转 -- 不错
查看>>
[Ogre][地形][原创]基于OgreTerrain的地形实现
查看>>
shell登录模式及其相应配置文件(转)
查看>>
Puppet常识梳理
查看>>
web.config配置文件中的configSource属性
查看>>
发现一个国内牛逼的maven仓库,速度真的太快了
查看>>
Snmp配置
查看>>
使用java实现CNN的实战
查看>>
大白话系列之C#委托与事件讲解(二)
查看>>
linux下使用 du查看某个文件或目录占用磁盘空间的大小
查看>>
iCheck表单美化插件使用方法详解(含参数、事件等)
查看>>
IOS UIAlertController 使用方法
查看>>
MySQL存储过程 事务transaction
查看>>