zoukankan      html  css  js  c++  java
  • Java基础——clone()方法浅析

    一、clone的概念  

      clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢? 

    1. 使用new操作符创建一个对象
    2. 使用clone方法复制一个对象 

      那么这两种方式有什么相同和不同呢?

    • new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
      1 Person p = new Person(23, "zhang");
      2 Person p1 = p;
      3  
      4 System.out.println(p);
      5 System.out.println(p1);

       当Person p1 = p;执行之后, 是创建了一个新的对象吗? 首先看打印结果:
        com.pansoft.zhangjg.testclone.Person@2f9ee1ac
        com.pansoft.zhangjg.testclone.Person@2f9ee1ac
      可以看出,打印的地址值是相同的,既然地址都是相同的,那么肯定是同一个对象。p和p1只是引用而已,他们都指向了一个相同的对象Person(23, "zhang") 。 可以把这种现象叫做引用的复制。 

    • 而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。注意:如果一个类没有复写clone()方法,则clone()方法默认的是返回一个Object对象,我们可以强制转化为我们需要的类。
      Person p = new Person(23, "zhang");
      Person p1 = (Person) p.clone();
       
      System.out.println(p);
      System.out.println(p1);

       从打印结果可以看出,两个对象的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量: 
        com.pansoft.zhangjg.testclone.Person@2f9ee1ac
        com.pansoft.zhangjg.testclone.Person@67f1fba0

    二、clone的用法  

      Java编程中经常会用到clone()方法,clone()方法是Object类的一个protected的方法,由于非静态所以不能直接调用,但是可以被子类调用。Object类的clone()方法会知道对象大小,为其分配足够的内存空间,并将其对象的内容复制到新的对象中。但是,clone()方法在执行其动作之前必须先检查class是否实现了Cloneable接口。Cloneable接口和Serialiable接口一样是一个标记接口,没有任何内容

      Java中clone()方法的含义是,假设x是一个非空的对象,则:

        x.clone() != x                  为true,就是说他们不是同一个对象

        x.clone(),getClass() == x.getClass()      为true,说明他们是同一类型的class

        x.equals(x.clone())                为true,说明逻辑上应该相同

      一个类的对象要想在调用clone()方法时不抛出CloneNotSupportedException,有两种方法:

    1. 该类在定义声明时声明实现了Cloneable接口,即在声明时加上“implements Cloneable”即可(浅拷贝
    2. 该类在定义时,声明实现Cloneable接口,并且复写clone()方法,并将其声明为public(深拷贝

    三、浅析clone()方法与浅拷贝、深拷贝概念

      在上面一节中我们发现正确使用clone()方法的两种方法的结果并不相同,直接实现Cloneable接口只是实现了浅拷贝,而复写clone()方法则可以实现深拷贝。那么,这种copy有什么区别呢?

      上面的示例代码中,Person中有两个成员变量,分别是name和age, name是String类型, age是int类型。代码非常简单,如下所示:

     1 public class Person implements Cloneable{
     2      
     3     private int age ;
     4     private String name;
     5      
     6     public Person(int age, String name) {
     7         this.age = age;
     8         this.name = name;
     9     }
    10      
    11     public Person() {}
    12  
    13     public int getAge() {
    14         return age;
    15     }
    16  
    17     public String getName() {
    18         return name;
    19     }
    20      
    21     @Override
    22     protected Object clone() throws CloneNotSupportedException {
    23         return (Person)super.clone();
    24     }
    25 }

      由于age是基本数据类型, 那么对它的拷贝没有什么疑议,直接将一个4字节的整数值拷贝过来就行。但是name是String类型的, 它只是一个引用, 指向一个真正的String对象,那么对它的拷贝有两种方式: 直接将源对象中的name的引用值拷贝给新对象的name字段, 或者是根据原Person对象中的name指向的字符串对象创建一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的name字段。这两种拷贝方式分别叫做浅拷贝和深拷贝。深拷贝和浅拷贝的原理如下图所示:

      下面通过代码进行验证。如果两个Person对象的name的地址值相同, 说明两个对象的name都指向同一个String对象, 也就是浅拷贝, 而如果两个对象的name的地址值不同, 那么就说明指向不同的String对象, 也就是在拷贝Person对象的时候, 同时拷贝了name引用的String对象, 也就是深拷贝。验证代码如下:

    Person p = new Person(23, "zhang");
    Person p1 = (Person) p.clone();
     
    System.out.println("p.getName().hashCode() : " + p.getName().hashCode());
    System.out.println("p1.getName().hashCode() : " + p1.getName().hashCode());
     
    String result = p.getName().hashCode() == p1.getName().hashCode() 
            ? "clone是浅拷贝的" : "clone是深拷贝的";
     
    System.out.println(result);

    打印结果为:

      p.getName().hashCode() : 115864556
      p1.getName().hashCode() : 115864556
      clone是浅拷贝的

      所以,clone方法执行的是浅拷贝, 在编写程序时要注意这个细节。 

    四、深拷贝的实现

      为了要在clone对象时进行深拷贝, 那么就要实现Clonable接口,覆盖并实现clone方法,除了调用父类中的clone方法得到新的对象, 还要将该类中的引用变量也clone出来。如果只是用Object中默认的clone方法,是浅拷贝的,再次以下面的代码验证:

     1 static class Body implements Cloneable{
     2     public Head head;
     3      
     4     public Body() {}
     5  
     6     public Body(Head head) {this.head = head;}
     7  
     8     @Override
     9     protected Object clone() throws CloneNotSupportedException {
    10         return super.clone();
    11     }
    12      
    13 }
    14 static class Head /*implements Cloneable*/{
    15     public  Face face;
    16      
    17     public Head() {}
    18     public Head(Face face){this.face = face;}
    19      
    20 } 
    21 public static void main(String[] args) throws CloneNotSupportedException {
    22      
    23     Body body = new Body(new Head());
    24      
    25     Body body1 = (Body) body.clone();
    26      
    27     System.out.println("body == body1 : " + (body == body1) );
    28      
    29     System.out.println("body.head == body1.head : " +  (body.head == body1.head));
    30          
    32 }

    在以上代码中, 有两个主要的类, 分别为Body和Face, 在Body类中, 组合了一个Face对象。当对Body对象进行clone时, 它组合的Face对象只进行浅拷贝。打印结果可以验证该结论:

      body == body1 : false
      body.head == body1.head : true


      如果要使Body对象在clone时进行深拷贝, 那么就要在Body的clone方法中,将源对象引用的Head对象也clone一份。 

     1 static class Body implements Cloneable{
     2     public Head head;
     3     public Body() {}
     4     public Body(Head head) {this.head = head;}
     5  
     6     @Override
     7     protected Object clone() throws CloneNotSupportedException {
     8         Body newBody =  (Body) super.clone();
     9         newBody.head = (Head) head.clone();
    10         return newBody;
    11     }
    12      
    13 }
    14 static class Head implements Cloneable{
    15     public  Face face;
    16      
    17     public Head() {}
    18     public Head(Face face){this.face = face;}
    19     @Override
    20     protected Object clone() throws CloneNotSupportedException {
    21         return super.clone();
    22     }
    23 } 
    24 public static void main(String[] args) throws CloneNotSupportedException {
    25      
    26     Body body = new Body(new Head());
    27      
    28     Body body1 = (Body) body.clone();
    29      
    30     System.out.println("body == body1 : " + (body == body1) );
    31      
    32     System.out.println("body.head == body1.head : " +  (body.head == body1.head));
    33      
    34      
    35 }

    打印结果为:

      body == body1 : false
      body.head == body1.head : true

      由此可见, body和body1内的head引用指向了不同的Head对象, 也就是说在clone Body对象的同时, 也拷贝了它所引用的Head对象, 进行了深拷贝

      但是,我们可以看到,Head类中的face对象的clone()是浅复制,所以这里肯定还会存在一个结果是body.head.face == body1.head.face : false,这表明这样写的clone()并不是很彻底,那么要进行完全彻底的深复制,我们应该写成这样:

     1 static class Head implements Cloneable{
     2     public  Face face;
     3      
     4     public Head() {}
     5     public Head(Face face){this.face = face;}
     6     @Override
     7     protected Object clone() throws CloneNotSupportedException {
     8         //return super.clone();
     9         Head newHead = (Head) super.clone();
    10         newHead.face = (Face) this.face.clone();
    11         return newHead;
    12     }
    13 } 
    14  
    15 static class Face implements Cloneable{
    16     @Override
    17     protected Object clone() throws CloneNotSupportedException {
    18         return super.clone();
    19     }
    20 }

      我们需要对对象中的每一个引用进行深拷贝才能彻底实现深拷贝。

  • 相关阅读:
    Balanced Binary Tree
    Convert Sorted List to Binary Search Tree
    Convert Sorted Array to Binary Search Tree
    Binary Tree Zigzag Level Order Traversal
    Validate Binary Search Tree
    Binary Tree Level Order Traversal II
    Binary Tree Level Order Traversal
    Maximum Depth of Binary Tree
    如何把U盘的两个盘或者多个盘合成一个
    bugku 想蹭网先解开密码
  • 原文地址:https://www.cnblogs.com/mukekeheart/p/5639641.html
Copyright © 2011-2022 走看看