第11条:谨慎的覆盖clone

Cloneable接口的目的是作为对象的一个mixin接口(mixin interface),表明这样的对象允许克隆(clone)。 
遗憾的是,他并没有成功的达到这个目的。其主要的缺陷在于,他缺少一个clone方法,Object的clone方法是受保护的。 
如果不借助于反射(reflection),就不能仅仅因为一个对戏那个实现了Cloneable,就可以调用clone。 

demo: 

  1. import java.util.LinkedList;  
  2. import java.util.List;  
  3.   
  4. public class A implements Cloneable {  
  5.     private Integer id;  
  6.     private String name;  
  7.     private List<String> phones = new LinkedList<String>();  
  8.       
  9.     public Integer getId() {  
  10.         return id;  
  11.     }  
  12.     public void setId(Integer id) {  
  13.         this.id = id;  
  14.     }  
  15.     public String getName() {  
  16.         return name;  
  17.     }  
  18.     public void setName(String name) {  
  19.         this.name = name;  
  20.     }  
  21.       
  22.     public List<String> getPhones() {  
  23.         return phones;  
  24.     }  
  25.     public void setPhones(List<String> phones) {  
  26.         this.phones = phones;  
  27.     }  
  28.     @Override  
  29.     protected A clone() {  
  30.         try {  
  31.             return (A)super.clone();  
  32.         } catch (CloneNotSupportedException e) {  
  33.             e.printStackTrace();  
  34.         }  
  35.         return null;  
  36.     }  
  37.       
  38.       
  39. }  
  40.   
  41.   
  42. public class HelloWorld {  
  43.   
  44.     public static void main(String[] args) {  
  45. //      cope1();  
  46.           
  47.         cope2();  
  48.     }  
  49.       
  50.     //在不借助于BeanUtils的实例复制方法下,会出现问题,当然,原因很简单,一个指针  
  51.     private static void cope1() {  
  52.         A old = new A();  
  53.         old.setId(1);  
  54.         old.setName("hello");  
  55.           
  56.         A n = old;  
  57.         n.setName("world");  
  58.           
  59.         System.out.println(old.getName());  
  60.     }  
  61.   
  62.     /** 
  63.      * 我们让A实现了clone。但是你如果A里面含有很多可变的对象,就比较麻烦了,会引用与原来相同的数组
  64. (BeanUtils.copyProperties也会引用与原来相同的数组)。 
  65.      */  
  66.     private static void cope2() {  
  67.         A old = new A();  
  68.         old.setId(1);  
  69.         old.setName("hello");  
  70.         old.getPhones().add("da");  
  71.           
  72.         A n = old.clone();  
  73.         n.setName("world");  
  74.         n.getPhones().set(0"xiao");  
  75.           
  76.         System.out.println(old.getName());  
  77.         System.out.println(old.getPhones().get(0));  
  78.     }  
  79. }  



语言之外的(extralinguistic)机制:无需调用构造器就可以创建对象。 

如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。 

实际上,对于实现了Cloneable的类,我们总是期望他也提供一个功能适当的公有的clone方法。 
通常情况下,除非该类的所有超类都提供了行为良好的clone实现,无论是共有的还是受保护的,否则,都不可能这么做。 


如果你想在一个类中实现Cloneable,首先他的超类都需要提供行为良好的clone方法。 
demo: 
  1. // Adding a clone method to PhoneNumber   
  2. import java.util.*;  
  3.   
  4. public final class PhoneNumber implements Cloneable {  
  5.     private final short areaCode;  
  6.     private final short prefix;  
  7.     private final short lineNumber;  
  8.   
  9.     public PhoneNumber(int areaCode, int prefix,  
  10.                        int lineNumber) {  
  11.         rangeCheck(areaCode,    999"area code");  
  12.         rangeCheck(prefix,      999"prefix");  
  13.         rangeCheck(lineNumber, 9999"line number");  
  14.         this.areaCode  = (short) areaCode;  
  15.         this.prefix  = (short) prefix;  
  16.         this.lineNumber = (short) lineNumber;  
  17.     }  
  18.   
  19.     private static void rangeCheck(int arg, int max,  
  20.                                    String name) {  
  21.         if (arg < 0 || arg > max)  
  22.            throw new IllegalArgumentException(name +": " + arg);  
  23.     }  
  24.   
  25.     @Override public boolean equals(Object o) {  
  26.         if (o == this)  
  27.             return true;  
  28.         if (!(o instanceof PhoneNumber))  
  29.             return false;  
  30.         PhoneNumber pn = (PhoneNumber)o;  
  31.         return pn.lineNumber == lineNumber  
  32.             && pn.prefix  == prefix  
  33.             && pn.areaCode  == areaCode;  
  34.     }  
  35.   
  36.     @Override public int hashCode() {  
  37.         int result = 17;  
  38.         result = 31 * result + areaCode;  
  39.         result = 31 * result + prefix;  
  40.         result = 31 * result + lineNumber;  
  41.         return result;  
  42.     }  
  43.   
  44.     /** 
  45.      * Returns the string representation of this phone number. 
  46.      * The string consists of fourteen characters whose format 
  47.      * is "(XXX) YYY-ZZZZ", where XXX is the area code, YYY is 
  48.      * the prefix, and ZZZZ is the line number.  (Each of the 
  49.      * capital letters represents a single decimal digit.) 
  50.      * 
  51.      * If any of the three parts of this phone number is too small 
  52.      * to fill up its field, the field is padded with leading zeros. 
  53.      * For example, if the value of the line number is 123, the last 
  54.      * four characters of the string representation will be "0123". 
  55.      * 
  56.      * Note that there is a single space separating the closing 
  57.      * parenthesis after the area code from the first digit of the 
  58.      * prefix. 
  59.      */  
  60.     @Override public String toString() {  
  61.         return String.format("(%03d) %03d-%04d",  
  62.                              areaCode, prefix, lineNumber);  
  63.     }  
  64.   
  65.     @Override public PhoneNumber clone() {  
  66.         try {  
  67.             return (PhoneNumber) super.clone();  
  68.         } catch(CloneNotSupportedException e) {  
  69.             throw new AssertionError();  // Can't happen  
  70.         }  
  71.     }  
  72.   
  73.     public static void main(String[] args) {  
  74.         PhoneNumber pn = new PhoneNumber(7078675309);  
  75.         Map<PhoneNumber, String> m  
  76.             = new HashMap<PhoneNumber, String>();  
  77.         m.put(pn, "Jenny");  
  78.         System.out.println(m.get(pn.clone()));  
  79.     }  
  80. }  


上面的clone方法返回的是PhoneNumber,而不是返回的Object,要记住,必须要在返回super.clone的结果之前将它转换。 


当被克隆的对象里面包含的域引用了可变的对象时: 
(实际上,clone方法就是另外一个构造器;你必须确保他能不会伤害到原始的对象,并确保正确的创建被克隆对象中的约束条件-invariant) 
  1. // A cloneable version of Stack   
  2. import java.util.Arrays;  
  3.   
  4. public class Stack implements Cloneable {  
  5.     private Object[] elements;  
  6.     private int size = 0;  
  7.     private static final int DEFAULT_INITIAL_CAPACITY = 16;  
  8.   
  9.     public Stack() {  
  10.         this.elements = new Object[DEFAULT_INITIAL_CAPACITY];  
  11.     }  
  12.   
  13.     public void push(Object e) {  
  14.         ensureCapacity();  
  15.         elements[size++] = e;  
  16.     }  
  17.   
  18.     public Object pop() {  
  19.         if (size == 0)  
  20.             throw new EmptyStackException();  
  21.         Object result = elements[--size];  
  22.         elements[size] = null// Eliminate obsolete reference  
  23.         return result;  
  24.     }  
  25.   
  26.     public boolean isEmpty() {  
  27.         return size == 0;  
  28.     }  
  29.   
  30.     @Override public Stack clone() {  
  31.         try {  
  32.             Stack result = (Stack) super.clone();  
  33.             result.elements = elements.clone();  
  34.             return result;  
  35.         } catch (CloneNotSupportedException e) {  
  36.             throw new AssertionError();  
  37.         }  
  38.     }  
  39.   
  40.     // Ensure space for at least one more element.  
  41.     private void ensureCapacity() {  
  42.         if (elements.length == size)  
  43.             elements = Arrays.copyOf(elements, 2 * size + 1);  
  44.     }  
  45.   
  46.     // To see that clone works, call with several command line arguments  
  47.     public static void main(String[] args) {  
  48.         args = new String[]{"hello","world"};  
  49.           
  50.         Stack stack = new Stack();  
  51.         for (String arg : args)  
  52.             stack.push(arg);  
  53.         Stack copy = stack.clone();  
  54.         while (!stack.isEmpty())  
  55.             System.out.print(stack.pop() + " ");  
  56.         System.out.println();  
  57.         while (!copy.isEmpty())  
  58.             System.out.print(copy.pop() + " ");  
  59.     }  
  60. }  



注意:如果上面的demo elements域是final的,上诉方式就不能正常的工作,clone方法是禁止给elements域赋新值的。 
clone架构与引用可变对象的final域的正常用法是不相兼容的。 


当被克隆的对象他有自己的散列桶数组,直接克隆就会出现问题,就需要单独的拷贝,如:
  1. 错误:  
  2. public HashTable clone(){  
  3.     HashTable result  = (HashTable)super.clone();  
  4.     result.buckets = buckets.clone();  
  5.     return reslut;  
  6. }  
  7. 正确:深度拷贝  
  8. public HashTable clone(){  
  9.     HashTable result  = (HashTable)super.clone();  
  10.     result.buckets = new Entry[buckets.length];  
  11.     for(int i=0;i<buckets.length;i++){  
  12.         if(buckets[i] != null){  
  13.             result.buckets[i] = buckets[i].deepCopy();  
  14.         }  
  15.     }  
  16.     return reslut;  
  17. }  


还要考虑在拷贝的过程中线程安全的问题,把被拷贝的对象设置为不可外部修改或者实现线程安全。 


最好提供某些其他的途径来代替对象拷贝,或者干脆不提供这样的功能。 


另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值