第11条:谨慎的覆盖clone

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

demo:

  1. importjava.util.LinkedList;
  2. importjava.util.List;
  3. publicclassAimplementsCloneable {
  4. privateInteger id;
  5. privateString name;
  6. privateList<String> phones =newLinkedList<String>();
  7. publicInteger getId() {
  8. returnid;
  9. }
  10. publicvoidsetId(Integer id) {
  11. this.id = id;
  12. }
  13. publicString getName() {
  14. returnname;
  15. }
  16. publicvoidsetName(String name) {
  17. this.name = name;
  18. }
  19. publicList<String> getPhones() {
  20. returnphones;
  21. }
  22. publicvoidsetPhones(List<String> phones) {
  23. this.phones = phones;
  24. }
  25. @Override
  26. protectedA clone() {
  27. try{
  28. return(A)super.clone();
  29. }catch(CloneNotSupportedException e) {
  30. e.printStackTrace();
  31. }
  32. returnnull;
  33. }
  34. }
  35. publicclassHelloWorld {
  36. publicstaticvoidmain(String[] args) {
  37. //cope1();
  38. cope2();
  39. }
  40. //在不借助于BeanUtils的实例复制方法下,会出现问题,当然,原因很简单,一个指针
  41. privatestaticvoidcope1() {
  42. A old =newA();
  43. old.setId(1);
  44. old.setName("hello");
  45. A n = old;
  46. n.setName("world");
  47. System.out.println(old.getName());
  48. }
  49. /**
  50. * 我们让A实现了clone。但是你如果A里面含有很多可变的对象,就比较麻烦了,会引用与原来相同的数组
  51. (BeanUtils.copyProperties也会引用与原来相同的数组)。
  52. */
  53. privatestaticvoidcope2() {
  54. A old =newA();
  55. old.setId(1);
  56. old.setName("hello");
  57. old.getPhones().add("da");
  58. A n = old.clone();
  59. n.setName("world");
  60. n.getPhones().set(0,"xiao");
  61. System.out.println(old.getName());
  62. System.out.println(old.getPhones().get(0));
  63. }
  64. }



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

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

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


如果你想在一个类中实现Cloneable,首先他的超类都需要提供行为良好的clone方法。
demo:
  1. // Adding a clone method to PhoneNumber
  2. importjava.util.*;
  3. publicfinalclassPhoneNumberimplementsCloneable {
  4. privatefinalshortareaCode;
  5. privatefinalshortprefix;
  6. privatefinalshortlineNumber;
  7. publicPhoneNumber(intareaCode,intprefix,
  8. intlineNumber) {
  9. rangeCheck(areaCode,999,"area code");
  10. rangeCheck(prefix,999,"prefix");
  11. rangeCheck(lineNumber,9999,"line number");
  12. this.areaCode= (short) areaCode;
  13. this.prefix= (short) prefix;
  14. this.lineNumber = (short) lineNumber;
  15. }
  16. privatestaticvoidrangeCheck(intarg,intmax,
  17. String name) {
  18. if(arg <0|| arg > max)
  19. thrownewIllegalArgumentException(name +": "+ arg);
  20. }
  21. @Overridepublicbooleanequals(Object o) {
  22. if(o ==this)
  23. returntrue;
  24. if(!(oinstanceofPhoneNumber))
  25. returnfalse;
  26. PhoneNumber pn = (PhoneNumber)o;
  27. returnpn.lineNumber == lineNumber
  28. && pn.prefix== prefix
  29. && pn.areaCode== areaCode;
  30. }
  31. @OverridepublicinthashCode() {
  32. intresult =17;
  33. result =31* result + areaCode;
  34. result =31* result + prefix;
  35. result =31* result + lineNumber;
  36. returnresult;
  37. }
  38. /**
  39. * Returns the string representation of this phone number.
  40. * The string consists of fourteen characters whose format
  41. * is "(XXX) YYY-ZZZZ", where XXX is the area code, YYY is
  42. * the prefix, and ZZZZ is the line number.(Each of the
  43. * capital letters represents a single decimal digit.)
  44. *
  45. * If any of the three parts of this phone number is too small
  46. * to fill up its field, the field is padded with leading zeros.
  47. * For example, if the value of the line number is 123, the last
  48. * four characters of the string representation will be "0123".
  49. *
  50. * Note that there is a single space separating the closing
  51. * parenthesis after the area code from the first digit of the
  52. * prefix.
  53. */
  54. @OverridepublicString toString() {
  55. returnString.format("(%03d) %03d-%04d",
  56. areaCode, prefix, lineNumber);
  57. }
  58. @OverridepublicPhoneNumber clone() {
  59. try{
  60. return(PhoneNumber)super.clone();
  61. }catch(CloneNotSupportedException e) {
  62. thrownewAssertionError();// Can't happen
  63. }
  64. }
  65. publicstaticvoidmain(String[] args) {
  66. PhoneNumber pn =newPhoneNumber(707,867,5309);
  67. Map<PhoneNumber, String> m
  68. =newHashMap<PhoneNumber, String>();
  69. m.put(pn,"Jenny");
  70. System.out.println(m.get(pn.clone()));
  71. }
  72. }


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


当被克隆的对象里面包含的域引用了可变的对象时:
(实际上,clone方法就是另外一个构造器;你必须确保他能不会伤害到原始的对象,并确保正确的创建被克隆对象中的约束条件-invariant)
  1. // A cloneable version of Stack
  2. importjava.util.Arrays;
  3. publicclassStackimplementsCloneable {
  4. privateObject[] elements;
  5. privateintsize =0;
  6. privatestaticfinalintDEFAULT_INITIAL_CAPACITY =16;
  7. publicStack() {
  8. this.elements =newObject[DEFAULT_INITIAL_CAPACITY];
  9. }
  10. publicvoidpush(Object e) {
  11. ensureCapacity();
  12. elements[size++] = e;
  13. }
  14. publicObject pop() {
  15. if(size ==0)
  16. thrownewEmptyStackException();
  17. Object result = elements[--size];
  18. elements[size] =null;// Eliminate obsolete reference
  19. returnresult;
  20. }
  21. publicbooleanisEmpty() {
  22. returnsize ==0;
  23. }
  24. @OverridepublicStack clone() {
  25. try{
  26. Stack result = (Stack)super.clone();
  27. result.elements = elements.clone();
  28. returnresult;
  29. }catch(CloneNotSupportedException e) {
  30. thrownewAssertionError();
  31. }
  32. }
  33. // Ensure space for at least one more element.
  34. privatevoidensureCapacity() {
  35. if(elements.length == size)
  36. elements = Arrays.copyOf(elements,2* size +1);
  37. }
  38. // To see that clone works, call with several command line arguments
  39. publicstaticvoidmain(String[] args) {
  40. args =newString[]{"hello","world"};
  41. Stack stack =newStack();
  42. for(String arg : args)
  43. stack.push(arg);
  44. Stack copy = stack.clone();
  45. while(!stack.isEmpty())
  46. System.out.print(stack.pop() +" ");
  47. System.out.println();
  48. while(!copy.isEmpty())
  49. System.out.print(copy.pop() +" ");
  50. }
  51. }



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


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


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


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


另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值