哈喽,我是狗哥。这是 Java 源码剖析的第三篇。克隆这个知识点在工作中使用不多,很容易被人忽略。但是面试中的面试官就很常问,因此小伙伴们还是要了解下。另外前两篇的链接在这里有兴趣的小伙伴可以看看:
2、工作三年,小胖连 HashMap 源码都没读过?真的菜!
❝克隆是指生物体通过体细胞进行的无性繁殖,以及由无性繁殖形成的基因型完全相同的后代个体。中学生物课本上的克隆羊多莉就属于这类生物。出处:百度百科 ❞
而在 Java 领域,克隆 Java 基础的一部分,它是指快速地构建出一个已有对象的副本。
「浅克隆(Shadow Clone)「是把原型对象中」成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象」,也就是「原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的」。
说句人话就是,浅克隆只会复制原型对象,但不会复制它所引用的对象,我了方便理解,我画张图:
Java 浅克隆
「深克隆(Deep Clone)「是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说」深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象」,画张图理解:
Java 深克隆
在 Java 中实现克隆,「首先要需要实现 Cloneable 接口、其次重写 Object 类的 clone () 方法」,代码如下:
**
* Project Name:review_java <br/>
* Package Name:com.nasus.clone <br/>
* Date:2021/1/31 20:10 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
public class CloneExample {
static class User implements Cloneable {
// 年龄
private Integer age;
// 名称
private String name;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
User userOne = new User();
userOne.setAge(22);
userOne.setName("clone");
// 克隆 userOne
User userTwo = (User) userOne.clone();
// 打印
System.out.println("userTwo: " + userTwo.getName());
}
}
运行结果:
userTwo: clone
要了解克隆的规则,就必须要从读源码开始,贴出源码:
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
因为是 native 方法,所以看代码没啥好看的。主要看注释,clone () 方法的规则主要有三条:
对于数组类型的克隆,我们一般使用 Arrays.copy () 实现,代码如下:
// 原型对象
User[] userArrayOne = {new User(22, "Java")};
// 克隆对象
User[] userArrayTwo = Arrays.copyOf(userArrayOne, userArrayOne.length);
// 修改克隆对象的第一个元素的值
userArrayTwo[0].setAge(25);
// 打印
System.out.println("userOne age : " + userArrayOne[0].getAge());
System.out.println("userTwo age : " + userArrayTwo[0].getAge());
运行结果:
userOne age : 25
userTwo age : 25
注意这段代码的第七句,我改变了克隆对象的 age 属性值。紧接着打印输出二者的 age 属性值。发现两个属性值是一样的。为什么呢?
深浅克隆的区别是浅克隆只会复制原型对象,并不会复制它所引用的对象。而深克隆会把引用的对象也给复制了。而在这里,「数组是原型对象,它所引用的对象是里面的 User。」
「我改变了克隆对象其中的 User 的 age,紧接着发现原型对象中的 User 的 age 属性也改变了。这就说明了,Arrays.copy () 是浅克隆,两个对象数组中的 User 只是地址值,它两都指向同一个 User 引用对象」。
如果是深克隆,不管是修改原型对象还是引克隆对象中的 User 属性值,另一个应当是不变的。
PS:反之,修改原型对象中的 User 的属性值,克隆对象中对应引用对象的属性值也会改变。
深克隆的实现方式很多,总的来说有以下几种:
/**
* Project Name:review_java <br/>
* Package Name:com.nasus.clone <br/>
* Date:2021/1/31 20:57 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
public class DeepCloneOneExample {
static class User implements Cloneable {
public User() {
}
public User(Integer age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
// 年龄
private Integer age;
// 名称
private String name;
// 地址
private Address address;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
// 引用类型克隆赋值
user.setAddress((Address) this.address.clone());
return user;
}
}
static class Address implements Cloneable {
public Address() {
}
public Address(String province, String city) {
this.province = province;
this.city = city;
}
// 省份
private String province;
// 城市
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
// 创建被赋值对象
Address address = new Address("广东", "广州");
User userOne = new User(22, "clone", address);
// 克隆 userOne 对象
User userTwo = (User) userOne.clone();
// 修改原型对象
userOne.getAddress().setCity("清远");
// 输出 p1 和 p2 地址信息
System.out.println("userOne:" + userOne.getAddress().getCity() +
",userTwo:" + userTwo.getAddress().getCity());
}
}
运行结果:
userOne:清远,userTwo:广州
可以看到,修改了原型对象的引用对象并没有改变克隆对象的引用对象。说明两者引用对象已经不是同一个引用对象了,所以是深克隆。
《Effective Java》 中「推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象」,实现代码如下:
/**
* Project Name:review_java <br/>
* Package Name:com.nasus.clone <br/>
* Date:2021/1/31 21:16 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
public class DeepCloneSecondExample {
static class User {
public User() {
}
public User(Integer age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
// 年龄
private Integer age;
// 名称
private String name;
// 地址
private Address address;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
static class Address {
public Address() {
}
public Address(String province, String city) {
this.province = province;
this.city = city;
}
// 省份
private String province;
// 城市
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address("广东", "广州");
User userOne = new User(22, "clone", address);
// 调用构造函数克隆对象
User userTwo = new User(userOne.getAge(), userOne.getName(),
new Address(userOne.getAddress().getProvince(), userOne.getAddress().getCity()));
// 修改原型对象
userOne.getAddress().setCity("清远");
// 输出 userOne 和 userOne 地址信息
System.out.println("userOne:" + userOne.getAddress().getCity() +
",userTwo:" + userTwo.getAddress().getCity());
}
}
运行结果:
userOne:清远,userTwo:广州
使用 JDK 自带字节流实现,先将要原型对象写入到内存中的字节流,再从这个字节流中读出刚刚存储的信息,作为一个新对象返回,此时这个新对象和原型对象就不存在任何地址上的共享,从而实现深克隆,代码如下:
/**
* Project Name:review_java <br/>
* Package Name:com.nasus.clone <br/>
* Date:2021/1/31 21:25 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
public class DeepCloneThirdExample {
static class User implements Serializable{
public User() {
}
public User(Integer age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
// 年龄
private Integer age;
// 名称
private String name;
// 地址
private Address address;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
static class Address implements Serializable {
public Address() {
}
public Address(String province, String city) {
this.province = province;
this.city = city;
}
// 省份
private String province;
// 城市
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
/**
* 通过字节流实现克隆
*/
static class StreamClone {
public static <T extends Serializable> T clone(User user) {
T cloneObj = null;
try {
// 写入字节流
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bo);
oos.writeObject(user);
oos.close();
// 分配内存,写入原始对象,生成新对象
// 获取上面的输出字节流
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
// 返回生成的新对象
cloneObj = (T) oi.readObject();
oi.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
// 创建对象
Address address = new Address("广东", "广州");
User userOne = new User(22, "clone", address);
// 调用构造函数克隆对象
User userTwo = StreamClone.clone(userOne);
// 修改原型对象
userOne.getAddress().setCity("清远");
// 输出 userOne 和 userOne 地址信息
System.out.println("userOne:" + userOne.getAddress().getCity() +
",userTwo:" + userTwo.getAddress().getCity());
}
}
运行结果:
userOne:清远,userTwo:广州
结果还是一样的,但这里要注意下。由于是通过字节流序列化实现的深克隆,所以每个对象必须能被序列化。也即必须实现 Serializable 接口。
比如:Apache Commons Lang,实现代码如下:
/**
* Project Name:review_java <br/>
* Package Name:com.nasus.clone <br/>
* Date:2021/1/31 21:41 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
public class DeepCloneFourthExample {
static class User implements Serializable {
public User() {
}
public User(Integer age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
// 年龄
private Integer age;
// 名称
private String name;
// 地址
private Address address;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
static class Address implements Serializable {
public Address() {
}
public Address(String province, String city) {
this.province = province;
this.city = city;
}
// 省份
private String province;
// 城市
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
public static void main(String[] args) {
// 创建对象
Address address = new Address("广东", "广州");
User userOne = new User(22, "clone", address);
// 调用 apache.commons.lang 克隆对象
User userTwo = SerializationUtils.clone(userOne);
// 修改原型对象
userOne.getAddress().setCity("清远");
// 输出 userOne 和 userOne 地址信息
System.out.println("userOne:" + userOne.getAddress().getCity() +
",userTwo:" + userTwo.getAddress().getCity());
}
}
输出结果:
userOne:清远,userTwo:广州
实际上,这种工作中我自己是用的比较多的一种方法。因为方便,它跟第三种有点像。其实底层是一样的,还是李用字节流实现。
比如 Gson 或者 FastJson 等等,下面以 Gson 为例,实现代码如下:
/**
* Project Name:review_java <br/>
* Package Name:com.nasus.clone <br/>
* Date:2021/1/31 21:58 <br/>
*
* @author <a href="turodog@foxmail.com">chenzy</a><br/>
*/
public class DeepCloneFifthExample {
static class User {
public User() {
}
public User(Integer age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
// 年龄
private Integer age;
// 名称
private String name;
// 地址
private Address address;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
static class Address {
public Address() {
}
public Address(String province, String city) {
this.province = province;
this.city = city;
}
// 省份
private String province;
// 城市
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
public static void main(String[] args) {
// 创建对象
Address address = new Address("广东", "广州");
User userOne = new User(22, "clone", address);
// 调用 Gson 克隆对象
Gson gson = new Gson();
User userTwo = gson.fromJson(gson.toJson(userOne), User.class);
// 修改原型对象
userOne.getAddress().setCity("清远");
// 输出 userOne 和 userTwo 地址信息
System.out.println("userOne:" + userOne.getAddress().getCity() +
",userTwo:" + userTwo.getAddress().getCity());
}
}
运行结果:
userOne:清远,userTwo:广州
这种方法会先把对象转化成字符串,再从字符串转化成新的对象,因为新对象是从字符串转化而来的,因此不会和原型对象有任何的关联,所以实现了深克隆。
本文介绍了深浅克隆的概念、区别;怎么实现客隆;克隆有啥约定俗成的规则;Arrays.copy () 是深克隆还是浅克隆;以及深刻龙的几种实现方式。希望对你有帮助~