引自:Java对象克隆(Clone)及Cloneable接口、Serializable接口的深入探讨
https://blog.csdn.net/kenthong/article/details/5758884
为了更好地解读,此文进行了再编辑,重新排版,并结合自己的理解做了一些修改,方便大家交流。
问题背景
谈到了对象的克隆,就不得不说为什么要对对象进行克隆。Java中所有的对象都是保存在堆中,而堆是供全局共享的。也就是说,如果同一个Java程序的不同方法,只要能拿到某个对象的引用,引用者就可以随意的修改对象的内部数据(前提是这个对象的内部数据通过get/set方法曝露出来)。有的时候,我们编写的代码想让调用者只获得该对象的一个拷贝(也就是一个内容完全相同的对象,但是在内存中存在两个这样的对象),有什么办法可以做到呢?当然是克隆咯。
举例说明
下面我们创建一个implements了java.lang.Cloneable
接口的类User,就是说User
这个类可以被克隆。
import java.util.Date;
public class User implements Cloneable {
private String username;
private String password;
private Date birthdate;
public User(String username, String password, Date birthdate) {
this.username = username;
this.password = password;
this.birthdate = birthdate;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public int hashCode() {
// 省略equals的实现(可用eclipse自动生成)
}
@Override
public boolean equals(Object obj) {
// 省略equals的实现(可用eclipse自动生成)
}
// 省略一大堆get/set方法
}
不懂Cloneable是什么,我们就回去点击查看这个接口的定义,然后我们发现,并没有什么,连一个方法都没有:
package java.lang;
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
我们可以看到虽然没有定义什么方法,但是Cloneable接口的注释里面前几行写了:
/*A class implements the <code>Cloneable</code> interface
to indicate to the {@link java.lang.Object#clone()} method
that it is legal for that method to make a field-for-field copy
of instances of that class.*/
也就是它明确说明: 一个实现了Cloneable接口的类会指示:该类可以使用Object中的clone()方法来实现实例的复制。由此我们获得明确指示,可以去探寻 Object 类中的 clone()方法:
/* @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方法,果然是个高深的东西,不过我们还是要占一下他的便宜。这个方法是protected的,分明就是叫我们去占便宜的。
再继续看看下面测试代码。
这里默认大家明白: == 和 equals() 的区别
==
: 完全相同(内容+引用)就是指向的是同一个东西,比如一个人有俩名字A和B,不管叫谁,说的都是他。
equals()
:相同的内容,不同的地址(引用不同) 就是相当于两个长得几乎一毛一样没有差异的的双胞胎,除了名字不一样都一样,但是他们毕竟不是同一个。
import java.util.Date;
import org.junit.Test;
public class TestCase {
@Test
public void testUserClone() throws CloneNotSupportedException {
User u1 = new User("Kent", "123456", new Date());
User u2 = u1;
User u3 = (User) u1.clone();
System.out.println(u1 == u2); // true
System.out.println(u1.equals(u2)); // true
System.out.println(u1 == u3); // false
System.out.println(u1.equals(u3)); // true
}
}
这个clone()方法果然牛,一下子就把我们的对象克隆了一份,执行结果也符合我们的预期,u1和u3的地址不同但是内容相同。
通过上述的例子,我们可以看出,要让一个对象进行克隆,其实就是两个步骤:
- 让该类实现java.lang.Cloneable接口;
- 重写(override)Object类的clone()方法。
但是,事实上真的是如此简单吗?再看下面的代码。
public class Administrator implements Cloneable {
private User user;
private Boolean editable;
public Administrator(User user, Boolean editable) {
this.user = user;
this.editable = editable;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public int hashCode() {
// 老规矩
}
@Override
public boolean equals(Object obj) {
// 老规矩
}
// 老规矩
}
上面定义了一个Administrator类,这个类持有一个User类的对象。接下来我们看看对Administrator对象进行克隆会有什么效果。
import java.util.Date;
import org.junit.Test;
public class TestCase {
@Test
public void testAdministratorClone() throws CloneNotSupportedException {
Administrator a1 = new Administrator(new User("Kent", "123456", new Date()), true);
Administrator a2 = a1;
Administrator a3 = (Administrator) a1.clone();
System.out.println(a1 == a2); // true
System.out.println(a1.equals(a2)); // true
System.out.println(a1 == a3); // false
System.out.println(a1.equals(a3)); // true
System.out.println(a1.getUser() == a3.getUser()); //true! It's not what we expected!!!!!
System.out.println(a1.getUser().equals(a3.getUser())); //true
}
}
呵呵呵!出问题了吧。Java哪是那么容易就能驾驭的说!
这里我们就可以引入两个专业的术语:浅克隆(shallow clone)和深克隆(deep clone)。
浅克隆 & 深克隆
所谓的浅克隆,顾名思义就是很表面的很表层的克隆,如果我们要克隆Administrator对象,只克隆他自身以及他包含的所有对象的引用地址。
而深克隆,就是非浅克隆、深层次的克隆。克隆除自身以外所有的对象,包括自身所包含的所有对象实例。至于深克隆的层次,由具体的需求决定,也有“N层克隆”一说。
但是,所有的基本(primitive)类型数据,无论是浅克隆还是深克隆,都会进行原值克隆。毕竟他们都不是对象,不是存储在堆中。注意:基本数据类型并不包括他们对应的包装类。
如果我们想让对象进行深度克隆,我们可以这样修改Administrator类。
@Override
protected Object clone() throws CloneNotSupportedException {
Administrator admin = (Administrator) super.clone();
admin.user = (User) admin.user.clone(); //层层克隆
return admin;
}
由于Boolean会对值进行缓存处理,所以我们没必要对Boolean的对象进行克隆。并且Boolean类也没有实现java.lang.Cloneable接口。
上面的深度克隆方法总结一下就是:
- 让该类实现java.lang.Cloneable接口;
- 确认持有的对象是否实现java.lang.Cloneable接口并提供clone()方法;
- 重写(override)Object类的clone()方法,并且在方法内部调用持有对象的clone()方法;
- ……
- 多麻烦啊,调来调去的,如果有N多个持有的对象,那就要写N多的方法,突然改变了类的结构,还要重新修改clone()方法。
难道就没有更好的办法吗?
答案是:我们可以考虑使用java.lang.Serializable
来实现对象的深度克隆。
用 java.lang.Serializable来实现对象的深度克隆。
首先,看一下Serializable是什么。
从名字我们知道,这是序列化的工具,序列化通俗的讲,是讲一个Object转化为更为底层的字节序列。以对其进行存储和传输等。详细介绍参见另一个文章《Java 序列化Serializable详解(附详细例子)》
https://blog.csdn.net/sheepmu/article/details/27579895
我们可以看一下Serializable
接口的定义(注释太长,我节选最有用的几行贴出来):
/*
* @author unascribed
* @see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Externalizable
* @since JDK1.1
*/
public interface Serializable {
}
从中我们可以看到和Cloneable·定义中类似的情况: 只要实现了Serializable 接口的类,都可以使用java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.ObjectOutput
java.io.ObjectInput
java.io.Externalizable
。
明白了Serializable是啥以后,我们将编写一个工具类并提供cloneTo()方法。这个工具类里面其实用的就是Serializable涉及到的那几个Object类中的方法,也就是真正实现 Serializable 功能的方法:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public abstract class BeanUtil {
@SuppressWarnings("unchecked")
public static T cloneTo(T src) throws RuntimeException {
ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
ObjectOutputStream out = null;
ObjectInputStream in = null;
T dist = null;
try {
out = new ObjectOutputStream(memoryBuffer);
out.writeObject(src);
out.flush();
in = new ObjectInputStream(new ByteArrayInputStream(memoryBuffer.toByteArray()));
dist = (T) in.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (out != null)
try {
out.close();
out = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
if (in != null)
try {
in.close();
in = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return dist;
}
}
看不懂,没关系,直接拿去用就可以了。嘻嘻。
接下来我们测试一下是否能通过这个工具来实现深度克隆。
import java.util.Date;
import org.junit.Test;
public class TestCase {
@Test
public void testCloneTo() {
Administrator src = new Administrator(new User("Kent", "123456", new Date()), true);
Administrator dist = BeanUtil.cloneTo(src);
System.out.println(src == dist); // false
System.out.println(src.equals(dist)); // true
System.out.println(src.getUser() == dist.getUser()); //false ! Well done!
System.out.println(src.getUser().equals(dist.getUser())); //true
}
}
好了,无论你的对象有多么的复杂,只要这些对象都能够实现java.lang.Serializable接口,就可以进行克隆,而且这种克隆的机制是JVM完成的,不需要修改实体类的代码,方便多了。