📜  Java中的Clone()方法

📅  最后修改于: 2020-04-01 06:13:35             🧑  作者: Mango

对象克隆是指创建对象的精确副本。它创建当前对象类的新实例,并使用该对象相应字段的内容初始化其所有字段。使用赋值运算符创建参考变量的副本
在Java中,没有运算符可以创建对象的副本。与C++不同,在Java中,如果我们使用赋值运算符,则它将创建引用变量副本而不是对象的副本。可以通过举例说明:

// Java 如果我们使用赋值运算符,则它将创建引用变量而不是对象的副本
import java.io.*;
// 测试代码
class Test
{
    int x, y;
    Test()
    {
        x = 10;
        y = 20;
    }
}
// 测试代码
class Main
{
    public static void main(String[] args)
    {
         Test ob1 = new Test();
         System.out.println(ob1.x + " " + ob1.y);
         // Creating a new reference variable ob2
         // pointing to same address as ob1
         Test ob2 = ob1;
         // Any change made in ob2 will be reflected
         // in ob1
         ob2.x = 100;
         System.out.println(ob1.x+" "+ob1.y);
         System.out.println(ob2.x+" "+ob2.y);
    }
}

输出:

10 20
100 20
100 20

使用clone()方法创建副本
要复制其对象的类必须在其或其父类之一中具有公共克隆方法。

  • 每个实现clone()的类都应调用super.clone()以获得克隆的对象引用。
  • 该类还必须实现java.lang.Cloneable接口,我们要创建其对象克隆,否则在该类的对象上调用clone方法时,它将抛出CloneNotSupportedException。
  • 语法:
    protected Object clone() throws CloneNotSupportedException

clone()方法的用法-浅拷贝

// Java程序展示clone()方法的用法-浅拷贝
import Java.util.ArrayList;
//  这个类的对象的引用,被包含在Test2中
class Test
{
    int x, y;
}
// 包含Test对象的引用,实现了浅拷贝的clone
class Test2 implements Cloneable
{
    int a;
    int b;
    Test c = new Test();
    public Object clone() throws
                   CloneNotSupportedException
    {
        return super.clone();
    }
}
// 测试代码
public class Main
{
    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();
       // 创建一个t1的拷贝,传给t2
       t2.a = 100;
       // 改变t2的数据类型,不会反应到t1
       t2.c.x = 300;
       // 改变对象字段的类型,会被反应到t1和t2(浅拷贝)
       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);
    }
}

输出:

10 20 300 40
100 20 300 40

在上面的示例中,t1.clone返回对象t1的浅拷贝。为了获得对象的深层副本,必须在获取副本后在克隆方法中进行某些修改。

深拷贝与浅拷贝对比:

  • 浅拷贝是一种复制对象的方法,默认情况下在克隆时采用。在此方法中,将旧对象X的字段复制到新对象Y。复制对象类型字段时,将引用复制到Y,即对象Y将指向X所指向的相同位置。如果字段值为基本类型,则复制基本类型的值。
  • 因此,对象X或Y中引用对象的任何更改都将反映在其他对象中。

浅拷贝易于制操作。在上面的示例中,我们创建了对象的浅拷贝。

clone()方法的用法–深度复制

  • 如果我们要创建对象X的深层副本并将其放置在新对象Y中,则将创建任何引用对象字段的新副本,并将这些引用放置在对象Y中。这意味着对对象X中引用对象字段所做的任何更改或Y将仅反映在该对象中,而不反映在另一个对象中。在下面的示例中,我们创建对象的深层副本。
  • 深层副本将复制所有字段,并复制这些字段所指向的动态分配的内存。当将对象及其引用的对象一起复制时,将发生深层复制。
    // Java展示深拷贝clone()
    import Java.util.ArrayList;
    // 这个类的对象的引用,被包含在Test2中
    class Test
    {
        int x, y;
    }
    // 包含Test对象的引用,实现了深拷贝的clone
    class Test2 implements Cloneable
    {
        int a, b;
        Test c = new Test();
        public Object clone() throws
                    CloneNotSupportedException
        {
            // 把浅拷贝赋值给引用t
            Test2 t = (Test2)super.clone();
            t.c = new Test();
            // 给字段c创建一个新的对象,并复制给浅拷贝,使之成为深拷贝
            return t;
        }
    }
    public class Main
    {
        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 t3 = (Test2)t1.clone();
           t3.a = 100;
           // t2的原始类型改变,不会反应到t1中
           t3.c.x = 300;
           // t2的原始类型改变,不会反应到t1中(深拷贝)
           System.out.println(t1.a + " " + t1.b + " " +
                              t1.c.x + " " + t1.c.y);
           System.out.println(t3.a + " " + t3.b + " " +
                              t3.c.x + " " + t3.c.y);
        }
    }

    输出:

    10 20 30 40
    100 20 300 0

    在上面的示例中,我们可以看到已经为Test类分配了一个新对象来复制将在clone方法中返回的对象,由于此t3将获得对象t1的深层副本。因此,t3对“ c”对象字段所做的任何更改都不会反映在t1中。
    克隆clone方法的优点:

    • 如果我们使用赋值运算符将对象引用分配给另一个引用变量,则它将指向旧对象的相同地址位置,并且不会创建该对象的新副本。因此,参考变量的任何更改都将反映在原始对象中。
    • 如果使用复制构造函数,则必须显式复制所有数据,即必须显式地重新分配该类的所有字段。但是在克隆方法中,创建新副本的工作是由方法本身完成的。因此,为了避免额外的处理,我们使用对象克隆。