zoukankan      html  css  js  c++  java
  • [改善Java代码]避免对象的浅拷贝

    建议43: 避免对象的浅拷贝

    我们知道一个类实现了Cloneable接口就表示它具备了被拷贝的能力,如果再覆写clone()方法就会完全具备拷贝能力。拷贝是在内存中进行的,所以在性能方面比直接通过new生成对象要快很多,特别是在大对象的生成上,这会使性能的提升非常显著。但是对象拷贝也有一个比较容易忽略的问题:浅拷贝(Shadow Clone,也叫做影子拷贝)存在对象属性拷贝不彻底的问题。我们来看这样一段代码:

     1 public class Client {  
     2     public static void main(String[] args) {  
     3          //定义父亲  
     4          Person f = new Person("父亲");  
     5          //定义大儿子  
     6          Person s1 = new Person("大儿子",f);  
     7          //小儿子的信息是通过大儿子拷贝过来的  
     8          Person s2 = s1.clone();  
     9          s2.setName("小儿子");  
    10          System.out.println(s1.getName() +" 的父亲是 " + s1.getFather().getName());  
    11          System.out.println(s2.getName() +" 的父亲是 " + s2.getFather().getName());  
    12     }  
    13 }  
    14 
    15 class Person implements Cloneable{  
    16     //姓名  
    17     private String name;  
    18     //父亲  
    19     private Person father;  
    20 
    21     public Person(String _name){  
    22          name = _name;  
    23     }  
    24     public Person(String _name,Person _parent){  
    25          name = _name;  
    26          father = _parent;  
    27     }  
    28     /*name和parent的getter/setter方法省略*/  
    29 
    30     //拷贝的实现  
    31     @Override  
    32     public Person clone(){  
    33          Person p = null;  
    34          try {  
    35            p = (Person) super.clone();  
    36          } catch (CloneNotSupportedException e) {  
    37            e.printStackTrace();  
    38          }  
    39          return p;  
    40    }
    41     public String getName() {
    42         return name;
    43     }
    44     public void setName(String name) {
    45         this.name = name;
    46     }
    47     public Person getFather() {
    48         return father;
    49     }
    50     public void setFather(Person father) {
    51         this.father = father;
    52     }  
    53 } 

    程序中,我们描述了这样一个场景:一个父亲,有两个儿子,大小儿子同根同种,所以小儿子对象就通过拷贝大儿子对象来生成,运行输出的结果如下:

    大儿子 的父亲是 父亲
    小儿子 的父亲是 父亲

    这很正确,没有问题。突然有一天,父亲心血来潮想让大儿子去认个干爹,也就是大儿子的父亲名称需要重新设置一下,代码如下:

     1 public static void main(String[] args) {  
     2      //定义父亲  
     3      Person f = new Person("父亲");  
     4      //定义大儿子  
     5      Person s1 = new Person("大儿子",f);  
     6      //小儿子的信息是通过大儿子拷贝过来的  
     7      Person s2 = s1.clone();  
     8      s2.setName("小儿子");  
     9      //认干爹  
    10      s1.getFather().setName("干爹");  
    11      System.out.println(s1.getName() +" 的父亲是 " + s1.getFather().getName());  
    12      System.out.println(s2.getName() +" 的父亲是 " + s2.getFather().getName());  
    13 } 

    上面仅仅修改了加粗字体部分,大儿子重新设置了父亲名称,我们期望的输出是:将大儿子父亲的名称修改为干爹,小儿子的父亲名称保持不变。下面来检查一下结果是否如此:

    大儿子 的父亲是 干爹  
    小儿子 的父亲是 干爹 

    怎么回事,小儿子的父亲也成了“干爹”?两个儿子都没有,岂不是要气死“父亲”了!出现这个问题的原因就在于clone方法,我们知道所有类都继承自Object,Object提供了一个对象拷贝的默认方法,即上面代码中的super.clone方法,但是该方法是有缺陷的,它提供的是一种浅拷贝方式,也就是说它并不会把对象的所有属性全部拷贝一份,而是有选择性的拷贝,它的拷贝规则如下:

    (1)基本类型

    如果变量是基本类型,则拷贝其值,比如int、float等。

    (2)对象

    如果变量是一个实例对象,则拷贝地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。这在Java中是很疯狂的,因为它突破了访问权限的定义:一个private修饰的变量,竟然可以被两个不同的实例对象访问,这让Java的访问权限体系情何以堪!

    (3)String字符串

    这个比较特殊,拷贝的也是一个地址,是个引用,但是在修改时,它会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,在此处我们可以认为String是一个基本类型。(有关字符串的知识详见第4章。)

    明白了这三个规则,上面的例子就很清晰了,小儿子对象是通过拷贝大儿子产生的,其父亲都是同一个人,也就是同一个对象,大儿子修改了父亲名称,小儿子也就跟着修改了—于是,父亲的两个儿子都没了!其实要更正也很简单,clone方法的代码如下:

    public Person clone(){  
         Person p = null;  
         try {  
            p = (Person) super.clone();  
            p.setFather(new Person(p.getFather().getName()));  
         } catch (CloneNotSupportedException e) {  
            e.printStackTrace();  
         }  
         return p;  
    } 

    然后再运行,小儿子的父亲就不会是“干爹”了。如此就实现了对象的深拷贝(Deep Clone),保证拷贝出来的对象自成一体,不受“母体”的影响,和new生成的对象没有任何区别。

    注意 浅拷贝只是Java提供的一种简单拷贝机制,不便于直接使用。

  • 相关阅读:
    2019 SDN阅读作业
    2019 SDN上机第3次作业
    SDN实验2
    SDN
    说好不肝---第五次作业
    [2020BUAA软工助教]助教每周小结(week 8)
    [2020BUAA软工助教]助教每周小结(week 7)
    [2020BUAA软工助教]助教每周小结(week 6)
    [2020BUAA软工助教]助教每周小结(week 5)
    [2020BUAA软工助教]助教每周小结(week 4)
  • 原文地址:https://www.cnblogs.com/DreamDrive/p/5430479.html
Copyright © 2011-2022 走看看