前言
在 Java 语言中,我们说两个对象是否相等通常有两层含义:
对象的内容是否相等,通常使用到对象的
equals(Object o)
函数;引用的地址是否相同,使用运算符
==
比较即可。
当两个对象通过赋值符号 =
赋值时,表明这两个对象指向了内存中同一个地址,所以改变其中一个对象的内容,也就间接地改变了另一个对象的内容。有时候,我们需要从一个已经存在的对象重新拷贝一份出来,并且不仅这两个对象内容相等,在内存中存在两个独立的存储地址,互不影响,这时,就需要用到 Java 中的克隆机制。
Cloneable
通过 Cloneable 接口可以很轻松地实现 Java 对象的克隆,只需要 implements Cloneable 并实现 Object 的 clone() 方法即可,如:
|
|
注意这里对象实现的是 Object 类的 clone() 方法,因为 Cloneable 是一个空接口:
|
|
从源码注释中可以看出,需要实现 Object 类中的 clone() 方法(注意:clone() 函数是一个 native 方法,同时抛出了一个异常):
|
|
从 clone() 函数的注释中能够看出对象与克隆对象之间的关系,测试代码如下(注意:我们在 User 对象中重写了 equals() 函数):
|
|
测试结果显示,通过 clone() 函数,我们成功地从 userOne 对象中克隆出了一份独立的 userThree 对象。
浅克隆与深克隆
谈此之前,我们先看一个例子,定义一个名为 Company 的类,并添加一个类型为 User 的成员变量:
|
|
测试代码及测试结果如下:
|
|
问题来了,companyThree 与 companyOne 中的 User 是同一个对象!也就是说 companyThree 只是克隆了 companyOne 的基本数据类型的数据,而对于引用类型的数据没有进行深度的克隆。也就是俗称的浅克隆。
浅克隆:顾名思义,就是很表层的克隆,只克隆对象自身的引用地址;
深克隆:也称“N层克隆”,克隆对象自身以及对象所包含的引用类型对象的引用地址。
这里需要注意的是,对于基本数据类型(primitive)和使用常量池方式创建的String 类型,都会针对原值克隆,所以不存在引用地址一说。当然不包括他们对应的包装类。
所以使用深克隆就可以解决上述 Company 对象克隆过后两个 user 对象的引用地址相同的问题。我们修改一下 Company 类的 clone() 函数:
|
|
再运行测试代码,就能得到 companyThree.getUser()==companyOne.getUser()
为 false 的结果了。
Serializable实现
通过上述介绍,我们知道,实现一个对象的克隆,需要如下几步:
对象所在的类实现 Cloneable 接口;
重写 clone() 函数,如果包涵引用类型的成员变量,需要使用深克隆。
如果对象不包含引用类型成员或者数量少的话,使用 Cloneable 接口还能接受,但当对象包含多个引用类型的成员,同时这些成员又包含了引用类型的成员,那层层克隆岂不是相当繁琐,并且维护不便?所以,这里介绍一种更加方便的实现方式,使用 ObjectOutputStream
和 ObjectOutputStream
来实现对象的序列化和反序列化:
|
|
只要要克隆的对象以及对象所包含的引用类型的成员对象所在的类实现了 java.io.Serializable
接口即可实现完美克隆。