对象的拷贝[深拷贝和浅拷贝]:
i.关于 clone[对象拷贝]——在实际编程过程,有时候我们会遇到一种情况:
当你有一个对象A,在某一个时刻,A 已经保存了对应的属性值,而且这些值本
身是有效的,这个时候可能需要一个和A 完全相同的对象B,并且当B 里面的
属性值发生变化的时候,A 中的属性值不受影响,可以理解为A 和B 独立,但
是B 的初始化不是按照我们平时创建该对象的时候的初始化操作,B 的初始化
数据完全来自A。对 Java 存储模型了解的人都明白,在 Java 里面如果针对两
个对象引用采取赋值操作的时候,仅仅是让两个引用指向了同一对象,如果其中
一个引用里面的对象属性改变的时候会影响另外一个对象属性跟着改变,所以
Java 语言本身的对象赋值语句是不能完成上边的需求的。在这种时候,就需要
用到 Object 类里面的通用方法 clone(),这里需要说明的是:
通过 clone()方法创建的对象是一个新对象,它可以认为是源对象的一个拷
贝,但是在内存堆中,JVM 会为这个拷贝分配新的对象存储空间来存放该对象
的所有状态
该拷贝和普通对象使用 new 操作符创建的对象唯一的区别在于初始值,这
个拷贝的初始值不是对象里面成员的默认值,而是和源对象此刻状态的成员的值
是一样的
下边这段代码是 clone 方法的运用:
public class Testing {
public static void main(String args[]){
AClass class1 = new AClass();
class1.a = 12;
AClass class2 = (AClass)class1.clone();
System.out.println(class2.a);
System.out.println(class1==class2);
}
}
class AClass implements Cloneable{
public int a = 0;
public Object clone(){
AClass o = null;
try{
o = (AClass)super.clone();
}catch(CloneNotSupportedException ex){
ex.printStackTrace();
}
return o;
}
}
上边这段代码运行结果输出为:
12
false
可以知道的就是成功复制了一个 AClass 的对象,该对象的引用为 class1,
而拷贝对象的引用为 class2,这两个引用通过==比较输出为 false,证明这两
个引用不是指向了同一个对象,而且拷贝对象里面的 a 的值和 class1 引用的对
象里面的 a 的值是一样的,都是 12,这样就成功完成了对象的拷贝过程。若你
对上边这段代码还有不了解的地方可以尝试将下边的代码修改掉:
AClass class2 = (AClass)class1.clone() 修改为:AClass class2 = class1;
改了过后,输出结果应该为:
12
true
所以在对象的 clone 过程,需要注意的几点有:
[1]希望能够提供对象 clone 功能的类必须实现 Cloneable 接口,这个接
口位于 java.lang 包里面
[2]希望提供对象 clone 功能的类必须重载 clone()方法,在重载过程可以
看到这句话:super.clone();也就是说,不论 clone 类的继承结构如何,我
们在对象拷贝的时候都直接或简介调用了 Object 的 clone()方法。而且细心留
意可以看到 Object 的 clone 方法是 protected 域的,也就是说这个方法只有
Object 的子类可以调用,而在重载的时候将 clone 方法修饰符改为 public
[3]还有一点很重要就是 Object 源代码里面的 clone()方法是 native 方法,
一般而言,对 JVM 来说,native 方法的效率远比 java 中的普通方法高,这就
是为什么我们在复制一个对象的时候使用 Object 的 clone()方法,而不是使用
new 的方式。
[4]Cloneable 接口和我们在编写 IO 程序的时候序列化接口一样,只是一
个标志,这个接口是不包含任何方法的,这个标志主要是为了检测 Object 类中
的 clone 方法,若我们定义的类想要实现拷贝功能,但是没有实现该接口而调
用 Object 的 clone 方法,那么就会出现语句中 catch 块里面的异常错误,抛出
CloneNotSupportedException。
ii 浅拷贝——在对象 clone 的过程中,浅拷贝又称为“影子 clone”,先看一
段代码:
//这里先定义一个类
class AClass{
public int a;
public AClass(int a){ this.a = a;}
public void change(){ a += 12;}
public String toString(){ return "A Value is " + this.a;}
}
//定义一个 clone 类,里面包含了 AClass 的对象引用
class BClass implements Cloneable{
public int a = 12;
public AClass obj = new AClass(11);
public Object clone(){
BClass object = null;
try{
object = (BClass)super.clone();
}catch(CloneNotSupportedException ex){
ex.printStackTrace();
}
return object;
}
}
public class TestClone {
public static void main(String args[]){
BClass class1 = new BClass();
class1.a = 15;
System.out.println(class1.a);
System.out.println(class1.obj);
BClass class2 = (BClass)class1.clone();
class2.a = 22;
class2.obj.change();
System.out.println(class1.a);
System.out.println(class1.obj);
System.out.println(class2.a);
System.out.println(class2.obj);
}
}
运行上边这段代码会有以下输出:
15
A Value is 11
15
A Value is 23
方法,所以不应该修改 class1 里面的 obj 里面的变量 a 的值【初衷】
22
A Value is 23
//这里拷贝成功了
//!!!不对,根本没有调用 class1 里面的 obj 的 change
不知细心的读者有没有发现输出和我们预期的拷贝不一样,虽然 class2 引
用的对象是从 class1 拷贝过来的,class2 里面的引用 obj 和 class1 里面的引
用 obj 实际上还是指向了同一个对象,其含义在于,拷贝的初衷是要复制一个一
模一样的对象,包括对象里面的对象也应该实现的是复制操作,它最终的目的是
保证 class1 和 class2 本身的属性以及 class1 和 class2 里面的对象引用的属
性在拷贝过后的各种相关操作里面相互独立,上边输出证明了 class1 和 class2
里面的变量 a 确实已经拷贝成功,但是 class1 和 class2 里面的 AClass 对象的
引用 obj 在拷贝过后还是指向了同一个对象,所以拷贝结束过后,调用 class2
的 obj 的 change 方法的时候,也修改了 class1 里面的 obj 指向的对象里面的
值。所以在 Java 里面我们把上边的拷贝过程称为“浅拷贝”,同样又称为“影子
clone”。
从这里可以知道,在 JVM 的对象复制里面,实际上基本数据类型可以直接
通过这种方式来进行拷贝工作,而非原始类型这样操作了过后拷贝的对象仅仅拷
贝了对象里面的基本数据类型的成员变量,而比较复杂的类型的成员变量并没有
像预期一样产生拷贝效果,这种拷贝我们就称之为“浅拷贝”
ii.深拷贝——如果要实现我们预期的对象拷贝效果,就需要使用深拷贝操作,其
实在浅拷贝基础上实现深拷贝有两个步骤,以上边的代码为例:
[1]第一步:让 AClass 实现同样的 clone 功能
[2]第二步:在 BClass 的 clone 操作中多写入一句话:object.obj =
(AClass)obj.clone();