原文:geeks4geeks
Java 中的 clone() 方法
对象 clone 是指创建对象的精确拷贝,它创建了一个当前对象新的实例并根据相应的值域的内容初始化所有的值域。
使用赋值操作符创建引用变量的拷贝
与 C++ 不同,没有运算符能创建对象的拷贝。如果使用赋值运算符它会创建当前对象引用的拷贝。这个可以见下面的例子:
// 简单的演示赋值运算符仅创建对象的引用的拷贝的例子
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);
// 创建一个新的引用变量 ob2 指向和 ob1 相同的内存地址
Test ob2 = ob1;
// 任何影响 ob2 的操作都会影响 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() 方法创建对象的拷贝
需要被创建拷贝的对象的类或父类必须有一个 public 的 clone()
方法。
- 每个实现
clone()
方法的类需要通过调用super.clone()
去获取克隆的对象的引用。 - 对于想创建对象拷贝的类必须实现
java.lang.Cloneable
接口,否则会在调用对象的clone
方法时抛出 CloneNotSupportedException 异常。
protected Object clone() throws CloneNotSupportedException
clone() 方法的用法 —— 浅拷贝
// 简单的演示使用 clone() 方法实现浅拷贝的例子
import java.util.ArrayList;
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;
// 创建 t1 的拷贝并赋值给 t2
Test2 t2 = (Test2)t1.clone();
// 改变 t2 中初始类型变量的值并不会影响 t1 的变量
t2.a = 100;
// 改变对象类型的值域会同时影响 t2 和 t1
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);
}
}
输出:
10 20 300 40
100 20 300 40
上例中,t1.clone 返回了 t1 对象的浅拷贝。为了获取对象的深拷贝,在获取拷贝之后还要在 clone()
方法中做一些修改。
深拷贝 vs 浅拷贝
- 浅拷贝 是一种默认情况下通过克隆的方式复制对象的方法,这个方法中,旧的对象值域 X 会被复制到新的对象值域 Y,同样会复制 X 的引用到新的 Y,即对象值域 Y 会指向和对象值域 X 相同的(内存)位置;假如值域是初始类型,则仅复制值域的值。
- 因此,任何对于对象 X 或对象 Y 的改变都会影响另一方。
clone() 方法的用法 —— 深拷贝
// 简单的演示使用 clone() 方法实现深拷贝的例子
import java.util.ArrayList;
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();
// 改变 t2 中初始类型的值域并不会影响 t1 的值域
t3.a = 100;
// 改变 t2 中的对象值域并不会影响 t1 的对象值域(深拷贝)
t3.c.x = 300;
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 类的对象被赋给 Test2 类的 clone 方法的返回值。由于 t3 包含对象 t1 的深拷贝。因此任何对于对象 t3 中的对象值域 c 的改变都不会影响 t1。
clone 方法的优势:
- 如果我们使用赋值操作符将一个对象的引用赋给另一个引用变量,这个引用变量将指向旧的对象,并没有新的对象创建。因此任何对于引用变量的改变将影响旧的对象。
- 如果我们使用拷贝构造方法,那么我们必须明确地复制所有的数据,例如我们需要在构造方法中给所有的值域重新赋值。但是在 clone 方法内部已经将这种创建新拷贝的方法实现,因此为了避免额外的工作我们使用对象克隆。
网友评论