当前位置:编程学习 > JAVA >>

Effective Java:Ch3_Methods:Item11_谨慎重写clone()

Cloneable接口的目的是作为对象的一个mixin接口,表明对象允许克隆;但这个目的没有达到。

其主要缺点是,Cloneable缺少一个clone()方法,而Object.clone()是受保护的。

 


通常,实现接口是为了表明类可以为它的客户做些什么;而Cloneable却是改变了超类中受保护方法的行为。

 


Object.clone()定义的约定:

 

[java]
/**
创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。一般来说,对于任何对象 x,如果表达式: 
x.clone() != x
是正确的,则表达式: 
x.clone().getClass() == x.getClass()将为 true,
但这些不是绝对条件。一般情况下是: 
x.clone().equals(x)将为 true,但这不是绝对条件。 
 
--按照惯例,返回的对象应该通过调用 super.clone 获得。
如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。 
 
 
--按照惯例,此方法返回的对象应该独立于该对象(正被克隆的对象)。
要获得此独立性,在 super.clone 返回对象之前,有必要对该对象的一个或多个字段进行修改。
这通常意味着要复制包含正在被克隆对象的内部“深层结构”的所有可变对象,并使用对副本的引用替换对这些对象的引用。
如果一个类只包含基本字段或对不变对象的引用,那么通常不需要修改 super.clone 返回的对象中的字段。 
 
 
Object 类的 clone 方法执行特定的克隆操作。
首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。
注意:所有的数组都被视为实现接口 Cloneable。
 
否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象相应字段的内容初始化该对象的所有字段;
这些字段的内容没有被自我克隆。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。 
 
Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。 
*/ 
 
protected native Object clone() throws CloneNotSupportedException; 
 
  

/**
创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。一般来说,对于任何对象 x,如果表达式:
x.clone() != x
是正确的,则表达式:
x.clone().getClass() == x.getClass()将为 true,
但这些不是绝对条件。一般情况下是:
x.clone().equals(x)将为 true,但这不是绝对条件。

--按照惯例,返回的对象应该通过调用 super.clone 获得。
如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。


--按照惯例,此方法返回的对象应该独立于该对象(正被克隆的对象)。
要获得此独立性,在 super.clone 返回对象之前,有必要对该对象的一个或多个字段进行修改。
这通常意味着要复制包含正在被克隆对象的内部“深层结构”的所有可变对象,并使用对副本的引用替换对这些对象的引用。
如果一个类只包含基本字段或对不变对象的引用,那么通常不需要修改 super.clone 返回的对象中的字段。


Object 类的 clone 方法执行特定的克隆操作。
首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。
注意:所有的数组都被视为实现接口 Cloneable。

否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象相应字段的内容初始化该对象的所有字段;
这些字段的内容没有被自我克隆。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。

Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。
*/

protected native Object clone() throws CloneNotSupportedException;

 

 

 

所有实现了Cloneable接口的类,都应该提供一个public的clone()方法;

在这个clone()方法中,首先调用super.clone(),然后修正任何需要修正的域。

例如Hashtable.clone()


[java] 
   /**
    * Creates a shallow copy of this hashtable. All the structure of the
    * hashtable itself is copied, but the keys and values are not cloned.
    * This is a relatively expensive operation.
    *
    * @return  a clone of the hashtable
    */ 
   public synchronized Object clone() { 
try { 
    Hashtable<K,V> t = (Hashtable<K,V>) super.clone(); // --super.clone()  
    t.table = new Entry[table.length]; 
    for (int i = table.length ; i-- > 0 ; ) { 
    t.table[i] = (table[i] != null) 
        ? (Entry<K,V>) table[i].clone() : null;    // --递归调用实例变量.clone()  
    } 
    t.keySet = null; 
    t.entrySet = null; 
           t.values = null; 
    t.modCount = 0; 
    return t; 
} catch (CloneNotSupportedException e) { 
    // this shouldn't happen, since we are Cloneable  
    throw new InternalError(); 

   } 

    /**
     * Creates a shallow copy of this hashtable. All the structure of the
     * hashtable itself is copied, but the keys and values are not cloned.
     * This is a relatively expensive operation.
     *
     * @return  a clone of the hashtable
     */
    public synchronized Object clone() {
 try {
     Hashtable<K,V> t = (Hashtable<K,V>) super.clone(); // --super.clone()
     t.table = new Entry[table.length];
     for (int i = table.length ; i-- > 0 ; ) {
  t.table[i] = (table[i] != null)
      ? (Entry<K,V>) table[i].clone() : null;    // --递归调用实例变量.clone()
     }
     t.keySet = null;
     t.entrySet = null;
            t.values = null;
     t.modCount = 0;
     return t;
 } catch (CloneNotSupportedException e) {
     // this shouldn't happen, since we are Cloneable
     throw new InternalError();
 }
    }注意,递归调用实例变量.clone()时,如果该变量为final,则不行!clone架构与饮用可变对象的final域的正常用法是不兼容的!

 

 

 

 

另一个实现对象拷贝的好办法是提供一个拷贝构造器(copy constructor)或者拷贝工厂(copy factory)。


例如:


[java] 
public Yum(Yum yum); 
public static Yum newInstance(Yum yum); 

public Yum(Yum yum);
public static Yum newInstance(Yum yum);
这种方法比clone有更多优势:

不依赖于某一种有风险的、语言之外的对象创建机制
不要求遵守尚未制定好文档的规范
不会与final域的正常使用发生冲突
不会抛出不必要的受检异常
不需要进行类型转换
另外,拷贝构造器和拷贝工厂可以带参数,参数类型一般是该类实现的接口,以便用户选择拷贝的实现类型。

例如,有一个HashSet s,希望把它拷贝成一个TreeSet,可以调用:new TreeSet(s)

 

[java] 
/**
 * Constructs a new tree set containing the elements in the specified
 * collection, sorted according to the <i>natural ordering</i> of its
 * elements.&nbs

补充:软件开发 , Java ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,