Java Clone()方法
对象克隆是指创建一个对象的精确拷贝。它创建了一个当前对象的类的新实例,并用这个对象的相应字段的内容来初始化它的所有字段。
使用赋值运算符来创建引用变量的副本
在Java中,没有操作符来创建一个对象的副本。与C++不同,在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);
}
}
输出
10 20
100 20
100 20
使用clone()方法创建一个副本
对象要被复制的类必须在它或它的一个父类中有一个公共的克隆方法。
- 每个实现clone()的类都应该调用super.clone()来获得克隆的对象引用。
- 类也必须实现java.lang.Cloneable接口,我们要创建的克隆对象必须是该类的,否则当对该类的对象调用克隆方法时,它将抛出CloneNotSupportedException。
- 语法。
protected Object clone() throws CloneNotSupportedException
clone()方法的用法–浅层拷贝
请注意 - 在下面的代码例子中,clone()方法确实创建了一个具有不同hashCode值的全新对象,这意味着它在一个单独的内存位置。但由于测试对象c在Test2内,原始类型已经实现了深度拷贝,但这个测试对象c仍在t1和t2之间共享。为了克服这个问题,我们明确地对对象变量c做了一个深度拷贝,这将在后面讨论。
// 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中引用对象字段的任何变化将只反映在该对象中,而不是其他对象中。在下面的例子中,我们创建一个对象的深度拷贝。
- 深度拷贝复制了所有字段,并复制了字段所指向的动态分配的内存。当一个对象与它所指向的对象一起被复制时,就会发生深度拷贝。
// 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
在上面的例子中,我们可以看到测试类的一个新对象被分配来复制一个将被返回到克隆方法的对象。由于这个原因,t3将获得一个对象t1的深度拷贝。因此,t3对’c’对象字段所做的任何改变,都不会反映在t1中。
clone方法的优点
- 如果我们使用赋值运算符将一个对象的引用分配给另一个引用变量,那么它将指向旧对象的相同地址位置,不会创建新的对象副本。由于这个原因,引用变量的任何变化都会反映在原始对象上。
- 如果我们使用复制构造函数,那么我们必须明确地复制所有的数据,也就是说,我们必须在构造函数中明确地重新分配类的所有域。但是在克隆方法中,创建一个新副本的工作由方法本身完成。所以为了避免额外的处理,我们使用对象克隆。