Effective Java笔记(13)谨慎地覆盖 clone

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

        既然 Cloneable 接口并没有包含任何方法,那么它到底有什么作用呢?它决定了 Object中受保护的 clone 方法实现的行为:如果一个类实现了 Cloneable,Object 的 clone方法就返回该对象的逐域拷贝,否则就会抛出 CloneNotSupportedException 异常 。

        虽然规范中没有明确指出,事实上,实现 Cloneable 接口的类是为了提供一个功能适当的公有的 clone 方法 。 为了达到这个目的,类及其所有超类都必须遵守一个相当复杂的、不可实施的,并且基本上没有文档说明的协议 。 由此得到一种语言之外的(extralinguistic)机制:它无须调用构造器就可以创建对象 。

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

        就像序列化一样,Cloneable 架构与引用可变对象的 final 域的正常用法是不相兼容的, 除非在原始对象和克隆对象之间可以安全地共享此可变对象 。

        克隆复杂对象的一种办法是,先调用 super. clone 方法,然后把结果对象中的所有域都设置成它们 的初始状态( initialstate ),然后调用高层( higher-level )的方法来重新产生对象的状态。 这种做法往往会产生一个简单、合理且相当优美的clone 方法,但是它运行起来通常没有“直接操作对象及其克隆对象的 内部状态的 clone方法快 。 虽然这种方法干脆利落,但它与整个Cloneable 架构是对立的,因为它完全抛弃了 Cloneable 架构基础的逐域对象复制的机制 。

像构造器一样,clone 方法也不应该在构造的过程中,调用可以覆盖的方法 。 如果 clone 调用了一个在子类中被覆盖的方法,那么在该方法所在的子类有机会修正它在克隆对象中的状态之前,
该方法就会先被执行,这样很有可能会导致克隆对象和原始对象之间的不一致 。

        Object 的 clone 方法被声明为可抛出 CloneNotSupportedException 异常,但是,覆盖版本的clone 方法可以忽略这个声明 。 公有的 clone 方法应该省略 throws 声明 ,因为不会抛出受检异常的方法使用起来更加轻松 。

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

        还有一点值得注意 。 如果你编写线程安全的类准备实现 Cloneable 接口,要记住它的 clone 方法必须得到严格的同步,就像任何其他方法一样 。Object的 clone 方法没有同步,即使很满意可能也必须编写同步的 clone 方法来调用 super.clone (),即实现 synchronized clone ()方法 。

        简而言之,所有实现了 Cloneable 接口的类都应该覆盖 clone 方法,并且是公有的方法,它的返回类型为类本身 。 该方法应该先调用 super.clone 方法,然后修正任何需要修正的域 。 一般情况下,这意味着要拷贝任何包含内部“深层结构”的可变对象,并用指向新对象的引用代替原来指向这些对象的引用 。 虽然,这些内部拷贝操作往往可以通过递归地调用 clone 来完成,但这通常并不是最佳方法 。 如果该类只包含基本类型的域,或者指向不可变对象的引用,那么多半的情况是没有域需要修正 。 这条规则也有例外 。 例如,代表序列号或其他唯一 ID 值的域,不管这些域是基本类型还是不可变的,它们也都需要被修正 。

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

你可能感兴趣的:(Effective,Java,java,开发语言,后端)