Java 中的 clone() 方法

yufei       6 年, 1 月 前       1908

对象克隆 ( object clone ) 是指创建对象的完整副本。它创建了当前对象的类的新实例,并使用当前对象的相应字段的内容初始化新实例的所有字段。

使用赋值运算符创建引用变量的副本

Java 语言中,不存在什么运算符可以用来创建对象的副本。

C++ 不同的是,Java 中的赋值运算符只会创建引用变量的副本,而不是对象的副本。

我们写一小段程序来演示下这一点

JavaTester.java

import java.io.*; 

class JavaTester 
{ 
    int x, y; 

    JavaTester() 
    { 
        x = 10; 
        y = 20; 
    } 

    public static void main(String[] args) 
    { 
        JavaTester ob1 = new JavaTester(); 

        System.out.println(ob1.x + " " + ob1.y); 

        // 赋值运算符创建了对象的引用
        // 也就是说 obj1 和 obj2 指向同一个对象
        JavaTester ob2 = ob1; 

        // 任何对 obj2 对象的变更都会反映到 obj1 上
        ob2.x = 100; 

        System.out.println(ob1.x+" "+ob1.y); 
        System.out.println(ob2.x+" "+ob2.y); 
    } 
} 

编译运行上面的代码,输出结果如下

[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester
10 20
100 20
100 20

使用 clone 方法创建对象的副本

要创建一个对象的完整副本,那么该对象所属的类或者类的父类必须存在一个 clone() 方法。

clone() 方法的实现有几个要求

  • 每个实现 clone() 的类都应该调用 super.clone() 来获取克隆的对象引用。
  • 实现了 clone() 方法的类还必须实现 java.lang.Cloneable 接口,否则当在该类的对象上调用 clone() 方法时,它将抛出 CloneNotSupportedException

clone() 方法的原型如下

protected Object clone() throws CloneNotSupportedException

使用 clone() 方法实现浅拷贝

我们先来看一段代码,该代码使用 clone() 方法实现了浅拷贝

import java.util.ArrayList; 


class Test 
{ 
    int x, y; 
} 

class Test2 implements Cloneable 
{ 
    int a; 
    int b; 
    Test c = new Test(); 
    public Object clone() throws CloneNotSupportedException 
    { 
        return super.clone(); 
    } 
} 

// Driver class 
public class JavaTester 
{ 
    public static void main(String args[]) throws
                          CloneNotSupportedException 
    { 
       Test2 t1 = new Test2(); 
       t1.a = 10; 
       t1.b = 20; 
       t1.c.x = 30; 
       t1.c.y = 40; 

       Test2 t2 = (Test2)t1.clone(); 

       t2.a = 100; 

       t2.c.x = 300; 

       System.out.println(t1.a + " " + t1.b + " " + 
                          t1.c.x + " " + t1.c.y); 
       System.out.println(t2.a + " " + t2.b + " " + 
                          t2.c.x + " " + t2.c.y); 
    } 
}

编译运行上面的代码,输出结果如下

[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester
10 20 300 40
100 20 300 40

上面这段代码中,t1.clone() 方法返回对象 t1 的浅拷贝,因为 t2 对象还是指向了原来的那个对象。

如果要实现一个深拷贝,则应该在 clone() 方法中对对象副本进行一些修改

深拷贝 vs 浅拷贝

浅拷贝是复制对象的方法,默认情况下是克隆。在该方法中,旧对象 X 的字段被复制到新对象 Y 。在复制对象类型字段时,引用被复制到 Y,即对象 Y 将指向与 X 指向相同的位置。如果字段值是基本类型,则复制基本类型的值。

因此,在浅拷贝中,对象 X 或 Y 中引用对象所做的任何更改都将反映在其他对象中。

使用 clone() 方法实现深拷贝

如果我们创建了对象 X 的一个深拷贝副本并将它赋值给对象 Y ,那么 Y 对象中的所有字段都是全新的。这意味着所有对对象 X 的变更或对对象 Y 的变更都不会互相影响到对方。

深拷贝会赋值所有的字段,也就是给所有的字段开辟一个全新的存储空间。

我们来写一个小范例,演示下如何创建深拷贝

JavaTester.java

import java.util.ArrayList; 


class Test 
{ 
    int x, y; 
} 

class Test2 implements Cloneable 
{ 
    int a; 
    int b; 
    Test c = new Test(); 
    public Object clone() throws CloneNotSupportedException 
    { 
        Test2 t = (Test2)super.clone(); 

        t.c = new Test(); 

        // 返回一个新的对象,因此会创建一个深拷贝 
        return t;
    } 
} 

// Driver class 
public class JavaTester 
{ 
    public static void main(String args[]) throws
                          CloneNotSupportedException 
    { 
       Test2 t1 = new Test2(); 
       t1.a = 10; 
       t1.b = 20; 
       t1.c.x = 30; 
       t1.c.y = 40; 

       Test2 t2 = (Test2)t1.clone(); 

       t2.a = 100; 

       t2.c.x = 300; 

       System.out.println(t1.a + " " + t1.b + " " + 
                          t1.c.x + " " + t1.c.y); 
       System.out.println(t2.a + " " + t2.b + " " + 
                          t2.c.x + " " + t2.c.y); 
    } 
}

编译运行以上代码,输出结果如下

[yufei@www.twle.cn java]$ javac JavaTester.java && java JavaTester
10 20 30 40
100 20 300 0

上面的代码中,我们将 super.clone() 方法返回的 Test2 对象赋值给 t 。到这一步,其实和浅拷贝没什么大的区别。

重要的区别在于吓一条语句 t.c = new Test() 。这条语句完成了 c 字段的深拷贝。

因为是深拷贝,因此对对象 t3 的修改并不会影响到 t1

clone() 方法的优点

  • 如果我们使用赋值运算符将对象引用分配给另一个引用变量,那么它将指向旧对象的相同地址位置,并且不会创建该对象的新副本。因此,引用变量的任何变化都将反映在原始对象中

  • 如果我们使用复制构造函数,那么我们必须显式复制所有数据,即我们必须在构造函数中明确重新分配类的所有字段。

  • 但是在 clone() 方法中,创建新副本的工作是由方法本身完成的。为了避免额外的处理,我们使用对象克隆。

后记

clone() 方法可以这么理解

  • java.lang.Object 中继承而来的 clone() 方法实现的是浅拷贝,因为它并不会对引用类型的字段进行深拷贝

  • 如果要实现深拷贝,则需要自己重写 clone() 方法,为每一个引用类型的字段创建一个全新的引用

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.