📜  Java中的 Clone() 方法

📅  最后修改于: 2022-05-13 01:55:03.405000             🧑  作者: Mango

Java中的 Clone() 方法

对象克隆是指创建对象的精确副本。它创建当前对象的类的一个新实例,并使用该对象的相应字段的内容来初始化其所有字段。

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

在Java中,没有创建对象副本的运算符。与 C++ 不同,在Java中,如果我们使用赋值运算符,那么它将创建引用变量的副本,而不是对象。这可以通过一个例子来解释。下面的程序演示了相同的内容。

Java
// Java program to demonstrate that assignment operator
// only creates a new reference to same object
import java.io.*;
 
// A test class whose objects are cloned
class Test {
    int x, y;
    Test()
    {
        x = 10;
        y = 20;
    }
}
 
// Driver Class
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);
    }
}


Java
// A Java program to demonstrate
// shallow copy using clone()
import java.util.ArrayList;
 
// An object reference of this class is
// contained by Test2
class Test {
    int x, y;
}
 
// Contains a reference of Test and
// implements clone with shallow copy.
class Test2 implements Cloneable {
    int a;
    int b;
    Test c = new Test();
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
}
 
// Driver class
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();
 
        // Creating a copy of object t1
        // and passing it to t2
        t2.a = 100;
 
        // Change in primitive type of t2 will
        // not be reflected in t1 field
        t2.c.x = 300;
 
        // Change in object type field will be
        // reflected in both t2 and t1(shallow copy)
        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);
    }
}


Java
// A Java program to demonstrate
// deep copy using clone()
 
// An object reference of this
// class is contained by Test2
class Test {
    int x, y;
}
 
// Contains a reference of Test and
// implements clone with deep copy.
class Test2 implements Cloneable {
    int a, b;
 
    Test c = new Test();
 
    public Object clone() throws CloneNotSupportedException
    {
        // Assign the shallow copy to
        // new reference variable t
        Test2 t = (Test2)super.clone();
 
        // Creating a deep copy for c
        t.c = new Test();
        t.c.x = c.x;
        t.c.y = c.y;
 
        // Create a new object for the field c
        // and assign it to shallow copy obtained,
        // to make it a deep copy
        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;
 
        // Change in primitive type of t2 will
        // not be reflected in t1 field
        t3.c.x = 300;
 
        // Change in object type field of t2 will
        // not be reflected in t1(deep copy)
        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
100 20
100 20

使用 clone() 方法创建副本

要制作其对象副本的类必须在其或其父类之一中具有公共克隆方法。

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

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

请注意——在下面的代码示例中,clone() 方法确实创建了一个具有不同 hashCode 值的全新对象,这意味着它位于单独的内存位置。但是由于Test对象c在Test2内部,原始类型已经实现了深拷贝,但是这个Test对象c仍然在t1和t2之间共享。为了克服这个问题,我们显式地对对象变量 c 进行深度复制,这将在后面讨论。

Java

// A Java program to demonstrate
// shallow copy using clone()
import java.util.ArrayList;
 
// An object reference of this class is
// contained by Test2
class Test {
    int x, y;
}
 
// Contains a reference of Test and
// implements clone with shallow copy.
class Test2 implements Cloneable {
    int a;
    int b;
    Test c = new Test();
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
}
 
// Driver class
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();
 
        // Creating a copy of object t1
        // and passing it to t2
        t2.a = 100;
 
        // Change in primitive type of t2 will
        // not be reflected in t1 field
        t2.c.x = 300;
 
        // Change in object type field will be
        // reflected in both t2 and t1(shallow copy)
        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 的浅表副本。要获得对象的深层副本,必须在获得副本后在 clone 方法中进行某些修改。

深拷贝与浅拷贝

  • 浅拷贝是复制对象的方法,克隆时默认遵循。在此方法中,旧对象 X 的字段被复制到新对象 Y。在复制对象类型字段时,引用被复制到 Y,即对象 Y 将指向 X 所指出的相同位置。如果字段值是原始类型,它复制原始类型的值。
  • 因此,对对象 X 或 Y 中的引用对象所做的任何更改都将反映在其他对象中。

浅拷贝既便宜又易于制作。在上面的示例中,我们创建了对象的浅表副本。

clone() 方法的使用——深拷贝

  • 如果我们要创建对象 X 的深层副本并将其放置在新对象 Y 中,则创建任何引用对象字段的新副本并将这些引用放置在对象 Y 中。这意味着对对象中引用的对象字段所做的任何更改X 或 Y 将仅反映在该对象中,而不会反映在另一个对象中。在下面的示例中,我们创建了对象的深层副本。
  • 深拷贝复制所有字段并复制字段指向的动态分配的内存。当一个对象连同它所引用的对象一起被复制时,就会发生深拷贝。

Java

// A Java program to demonstrate
// deep copy using clone()
 
// An object reference of this
// class is contained by Test2
class Test {
    int x, y;
}
 
// Contains a reference of Test and
// implements clone with deep copy.
class Test2 implements Cloneable {
    int a, b;
 
    Test c = new Test();
 
    public Object clone() throws CloneNotSupportedException
    {
        // Assign the shallow copy to
        // new reference variable t
        Test2 t = (Test2)super.clone();
 
        // Creating a deep copy for c
        t.c = new Test();
        t.c.x = c.x;
        t.c.y = c.y;
 
        // Create a new object for the field c
        // and assign it to shallow copy obtained,
        // to make it a deep copy
        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;
 
        // Change in primitive type of t2 will
        // not be reflected in t1 field
        t3.c.x = 300;
 
        // Change in object type field of t2 will
        // not be reflected in t1(deep copy)
        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 40

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

克隆方法的优点:

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