Part II<br>
谈到了对象的克隆,就不得不说为什么要对对象进行克隆。Java中所有的对象都是保存在堆中,而堆是供全局共享的。也就是说,如果同一个Java程序的不同方法,只要能拿到某个对象的引用,引用者就可以随意的修改对象的内部数据(前提是这个对象的内部数据通过get/set方法曝露出来)。有的时候,我们编写的代码想让调用者只获得该对象的一个拷贝(也就是一个内容完全相同的对象,但是在内存中存在两个这样的对象),有什么办法可以做到呢?当然是克隆咯。
Part III
首先,我们是程序员,当然是用我们程序员的语言来交流。
import java.util.Date;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public class User implements Cloneable {
Salin selepas log masuk
private String username;
Salin selepas log masuk
private String password;
Salin selepas log masuk
private Date birthdate;
Salin selepas log masuk
public User(String username, String password, Date birthdate) {
Salin selepas log masuk
this.username = username;
Salin selepas log masuk
this.password = password;
Salin selepas log masuk
this.birthdate = birthdate;
Salin selepas log masuk
@Override
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
protected Object clone() throws CloneNotSupportedException {
Salin selepas log masuk
Salin selepas log masuk
return super.clone();
Salin selepas log masuk
Salin selepas log masuk
@Override
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public int hashCode() {
Salin selepas log masuk
Salin selepas log masuk
// 省略equals的实现(可用eclipse自动生成)
Salin selepas log masuk
Salin selepas log masuk
@Override
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public boolean equals(Object obj) {
Salin selepas log masuk
Salin selepas log masuk
// 省略equals的实现(可用eclipse自动生成)
Salin selepas log masuk
Salin selepas log masuk
// 省略一大堆get/set方法
Salin selepas log masuk
上述代码构建了一个User类,并且实现了java.lang.Cloneable接口。顾名思义,Cloneable的意思就是说明这个类可以被克隆的意思。
而我们先去看看java.lang.Cloneable这个接口有些什么。
/*
Salin selepas log masuk
Salin selepas log masuk
* @(#)Cloneable.java 1.17 05/11/17
Salin selepas log masuk
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
Salin selepas log masuk
Salin selepas log masuk
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
Salin selepas log masuk
Salin selepas log masuk
*/
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
package java.lang;
Salin selepas log masuk
Salin selepas log masuk
/**
Salin selepas log masuk
Salin selepas log masuk
* A class implements the Cloneable interface to
Salin selepas log masuk
* indicate to the {@link java.lang.Object#clone()} method that it
Salin selepas log masuk
* is legal for that method to make a
Salin selepas log masuk
* field-for-field copy of instances of that class.
Salin selepas log masuk
* Invoking Object's clone method on an instance that does not implement the
Salin selepas log masuk
* Cloneable interface results in the exception
Salin selepas log masuk
* CloneNotSupportedException being thrown.
Salin selepas log masuk
* By convention, classes that implement this interface should override
Salin selepas log masuk
* Object.clone (which is protected) with a public method.
Salin selepas log masuk
* See {@link java.lang.Object#clone()} for details on overriding this
Salin selepas log masuk
* method.
Salin selepas log masuk
* Note that this interface does not contain the clone method.
Salin selepas log masuk
* Therefore, it is not possible to clone an object merely by virtue of the
Salin selepas log masuk
* fact that it implements this interface. Even if the clone method is invoked
Salin selepas log masuk
* reflectively, there is no guarantee that it will succeed.
Salin selepas log masuk
* @author unascribed
Salin selepas log masuk
Salin selepas log masuk
* @version 1.17, 11/17/05
Salin selepas log masuk
* @see java.lang.CloneNotSupportedException
Salin selepas log masuk
* @see java.lang.Object#clone()
Salin selepas log masuk
* @since JDK1.0
Salin selepas log masuk
Salin selepas log masuk
*/
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public interface Cloneable {
Salin selepas log masuk
不要惊讶,没错,除了一大堆的鸡肠以外,这个接口没有定义任何的方法签名。也就是说,我们要克隆一个对象,但是他又不给我提供一个方法。那该怎么办呢?不怕,我们还有全能的Object类,别忘记他可是所有类的始祖啊(神一般的存在着),所以,有事没事都该去问候一下他老人家。
/*
Salin selepas log masuk
Salin selepas log masuk
* @(#)Object.java 1.73 06/03/30
Salin selepas log masuk
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
Salin selepas log masuk
Salin selepas log masuk
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
Salin selepas log masuk
Salin selepas log masuk
*/
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
package java.lang;
Salin selepas log masuk
Salin selepas log masuk
/**
Salin selepas log masuk
Salin selepas log masuk
* Class Object is the root of the class hierarchy.
Salin selepas log masuk
* Every class has Object as a superclass. All objects,
Salin selepas log masuk
* including arrays, implement the methods of this class.
Salin selepas log masuk
* @author unascribed
Salin selepas log masuk
Salin selepas log masuk
* @version 1.73, 03/30/06
Salin selepas log masuk
* @see java.lang.Class
Salin selepas log masuk
* @since JDK1.0
Salin selepas log masuk
Salin selepas log masuk
*/
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public class Object {
Salin selepas log masuk
<br>
Salin selepas log masuk
// 省略N多的代码
Salin selepas log masuk
/**
Salin selepas log masuk
* Creates and returns a copy of this object. The precise meaning
Salin selepas log masuk
* of "copy" may depend on the class of the object. The general
Salin selepas log masuk
* intent is that, for any object x, the expression:
Salin selepas log masuk
* x.clone() != x
Salin selepas log masuk
* will be true, and that the expression:
Salin selepas log masuk
* x.clone().getClass() == x.getClass()
Salin selepas log masuk
* will be true, but these are not absolute requirements.
Salin selepas log masuk
* While it is typically the case that:
Salin selepas log masuk
* x.clone().equals(x)
Salin selepas log masuk
* will be true, this is not an absolute requirement.
Salin selepas log masuk
* By convention, the returned object should be obtained by calling
Salin selepas log masuk
* super.clone. If a class and all of its superclasses (except
Salin selepas log masuk
* Object) obey this convention, it will be the case that
Salin selepas log masuk
* x.clone().getClass() == x.getClass().
Salin selepas log masuk
* By convention, the object returned by this method should be independent
Salin selepas log masuk
* of this object (which is being cloned). To achieve this independence,
Salin selepas log masuk
* it may be necessary to modify one or more fields of the object returned
Salin selepas log masuk
* by super.clone before returning it. Typically, this means
Salin selepas log masuk
* copying any mutable objects that comprise the internal "deep structure"
Salin selepas log masuk
* of the object being cloned and replacing the references to these
Salin selepas log masuk
* objects with references to the copies. If a class contains only
Salin selepas log masuk
* primitive fields or references to immutable objects, then it is usually
Salin selepas log masuk
* the case that no fields in the object returned by super.clone
Salin selepas log masuk
* need to be modified.
Salin selepas log masuk
* The method clone for class Object performs a
Salin selepas log masuk
* specific cloning operation. First, if the class of this object does
Salin selepas log masuk
* not implement the interface Cloneable, then a
Salin selepas log masuk
* CloneNotSupportedException is thrown. Note that all arrays
Salin selepas log masuk
* are considered to implement the interface Cloneable.
Salin selepas log masuk
* Otherwise, this method creates a new instance of the class of this
Salin selepas log masuk
* object and initializes all its fields with exactly the contents of
Salin selepas log masuk
* the corresponding fields of this object, as if by assignment; the
Salin selepas log masuk
* contents of the fields are not themselves cloned. Thus, this method
Salin selepas log masuk
* performs a "shallow copy" of this object, not a "deep copy" operation.
Salin selepas log masuk
* The class Object does not itself implement the interface
Salin selepas log masuk
* Cloneable, so calling the clone method on an object
Salin selepas log masuk
* whose class is Object will result in throwing an
Salin selepas log masuk
* exception at run time.
Salin selepas log masuk
* @return a clone of this instance.
Salin selepas log masuk
* @exception CloneNotSupportedException if the object's class does not
Salin selepas log masuk
* support the Cloneable interface. Subclasses
Salin selepas log masuk
* that override the clone method can also
Salin selepas log masuk
* throw this exception to indicate that an instance cannot
Salin selepas log masuk
* be cloned.
Salin selepas log masuk
* @see java.lang.Cloneable
Salin selepas log masuk
*/
Salin selepas log masuk
protected native Object clone() throws CloneNotSupportedException;
Salin selepas log masuk
呵呵,又是一大串的鸡肠,别以为我是来凑字数的,这些都是Sun公司Java开发人员写的技术文章,多看看少说话吧。
没错,又是个native方法,果然是个高深的东西,不过我们还是要占一下他的便宜。而且他这个方法是protected的,分明就是叫我们去占便宜的。
再继续看看下面测试代码。
import java.util.Date;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
import org.junit.Test;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public class TestCase {
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
@Test
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public void testUserClone() throws CloneNotSupportedException {
Salin selepas log masuk
User u1 = new User("Kent", "123456", new Date());
Salin selepas log masuk
User u2 = u1;
Salin selepas log masuk
User u3 = (User) u1.clone();
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
System.out.println(u1 == u2); // true
Salin selepas log masuk
System.out.println(u1.equals(u2)); // true
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
System.out.println(u1 == u3); // false
Salin selepas log masuk
System.out.println(u1.equals(u3)); // true
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
这个clone()方法果然牛,一下子就把我们的对象克隆了一份,执行结果也符合我们的预期,u1和u3的地址不同但是内容相同。
Part IV
通过上述的例子,我们可以看出,要让一个对象进行克隆,其实就是两个步骤:
1. 让该类实现java.lang.Cloneable接口;
2. 重写(override)Object类的clone()方法。
但是,事实上真的是如此简单吗?再看下面的代码。
public class Administrator implements Cloneable {
Salin selepas log masuk
private User user;
Salin selepas log masuk
private Boolean editable;
Salin selepas log masuk
public Administrator(User user, Boolean editable) {
Salin selepas log masuk
this.user = user;
Salin selepas log masuk
this.editable = editable;
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
@Override
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
protected Object clone() throws CloneNotSupportedException {
Salin selepas log masuk
Salin selepas log masuk
return super.clone();
Salin selepas log masuk
Salin selepas log masuk
@Override
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public int hashCode() {
Salin selepas log masuk
Salin selepas log masuk
// 老规矩
Salin selepas log masuk
Salin selepas log masuk
@Override
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public boolean equals(Object obj) {
Salin selepas log masuk
Salin selepas log masuk
// 老规矩
Salin selepas log masuk
Salin selepas log masuk
// 老规矩
Salin selepas log masuk
上面定义了一个Administrator类,这个类持有一个User类的对象。接下来我们看看对Administrator对象进行克隆会有什么效果。
import java.util.Date;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
import org.junit.Test;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public class TestCase {
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
@Test
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public void testAdministratorClone() throws CloneNotSupportedException {
Salin selepas log masuk
Administrator a1 = new Administrator(new User("Kent", "123456", new Date()), true);
Salin selepas log masuk
Administrator a2 = a1;
Salin selepas log masuk
Administrator a3 = (Administrator) a1.clone();
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
System.out.println(a1 == a2); // true
Salin selepas log masuk
System.out.println(a1.equals(a2)); // true
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
System.out.println(a1 == a3); // false
Salin selepas log masuk
System.out.println(a1.equals(a3)); // true
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
System.out.println(a1.getUser() == a3.getUser()); //true ! It's not our expected!!!!!
Salin selepas log masuk
System.out.println(a1.getUser().equals(a3.getUser())); //true
Salin selepas log masuk
呵呵呵!出问题了吧。Java哪是那么容易就能驾驭的说!
这里我们就可以引入两个专业的术语:浅克隆(shallow clone)和深克隆(deep clone)。
所谓的浅克隆,顾名思义就是很表面的很表层的克隆,如果我们要克隆Administrator对象,只克隆他自身以及他包含的所有对象的引用地址。
而深克隆,就是非浅克隆。克隆除自身以外所有的对象,包括自身所包含的所有对象实例。至于深克隆的层次,由具体的需求决定,也有“N层克隆”一说。
但是,所有的基本(primitive)类型数据,无论是浅克隆还是深克隆,都会进行原值克隆。毕竟他们都不是对象,不是存储在堆中。注意:基本数据类型并不包括他们对应的包装类。
如果我们想让对象进行深度克隆,我们可以这样修改Administrator类。
@Override
Salin selepas log masuk
protected Object clone() throws CloneNotSupportedException {
Salin selepas log masuk
Administrator admin = (Administrator) super.clone();
Salin selepas log masuk
admin.user = (User) admin.user.clone();
Salin selepas log masuk
return admin;
Salin selepas log masuk
由于Boolean会对值进行缓存处理,所以我们没必要对Boolean的对象进行克隆。并且Boolean类也没有实现java.lang.Cloneable接口。
Part V
1. 让该类实现java.lang.Cloneable接口;
2. 确认持有的对象是否实现java.lang.Cloneable接口并提供clone()方法;
3. 重写(override)Object类的clone()方法,并且在方法内部调用持有对象的clone()方法;
4. ……
5. 多麻烦啊,调来调去的,如果有N多个持有的对象,那就要写N多的方法,突然改变了类的结构,还要重新修改clone()方法。
难道就没有更好的办法吗?
Part VI
接下来要重点介绍一下使用java.lang.Serializable来实现对象的深度克隆。
首先,我们编写一个工具类并提供cloneTo()方法。
import java.io.ByteArrayInputStream;
Salin selepas log masuk
import java.io.ByteArrayOutputStream;
Salin selepas log masuk
import java.io.IOException;
Salin selepas log masuk
import java.io.ObjectInputStream;
Salin selepas log masuk
import java.io.ObjectOutputStream;
Salin selepas log masuk
public abstract class BeanUtil {
Salin selepas log masuk
@SuppressWarnings("unchecked")
Salin selepas log masuk
public static T cloneTo(T src) throws RuntimeException {
Salin selepas log masuk
ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
Salin selepas log masuk
ObjectOutputStream out = null;
Salin selepas log masuk
ObjectInputStream in = null;
Salin selepas log masuk
T dist = null;
Salin selepas log masuk
try {
Salin selepas log masuk
out = new ObjectOutputStream(memoryBuffer);
Salin selepas log masuk
out.writeObject(src);
Salin selepas log masuk
out.flush();
Salin selepas log masuk
in = new ObjectInputStream(new ByteArrayInputStream(memoryBuffer.toByteArray()));
Salin selepas log masuk
dist = (T) in.readObject();
Salin selepas log masuk
} catch (Exception e) {
Salin selepas log masuk
throw new RuntimeException(e);
Salin selepas log masuk
} finally {
Salin selepas log masuk
if (out != null)
Salin selepas log masuk
try {
Salin selepas log masuk
Salin selepas log masuk
out.close();
Salin selepas log masuk
out = null;
Salin selepas log masuk
} catch (IOException e) {
Salin selepas log masuk
Salin selepas log masuk
throw new RuntimeException(e);
Salin selepas log masuk
Salin selepas log masuk
if (in != null)
Salin selepas log masuk
try {
Salin selepas log masuk
Salin selepas log masuk
in.close();
Salin selepas log masuk
in = null;
Salin selepas log masuk
} catch (IOException e) {
Salin selepas log masuk
Salin selepas log masuk
throw new RuntimeException(e);
Salin selepas log masuk
Salin selepas log masuk
return dist;
Salin selepas log masuk
看不懂,没关系,直接拿去用就可以了。嘻嘻。
接下来我们测试一下是否能通过这个工具来实现深度克隆。
又是这个可爱的TestCase,可怜的每次都要动他……
import java.util.Date;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
import org.junit.Test;
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public class TestCase {
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
@Test
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
public void testCloneTo() {
Salin selepas log masuk
Administrator src = new Administrator(new User("Kent", "123456", new Date()), true);
Salin selepas log masuk
Administrator dist = BeanUtil.cloneTo(src);
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
System.out.println(src == dist); // false
Salin selepas log masuk
System.out.println(src.equals(dist)); // true
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
System.out.println(src.getUser() == dist.getUser()); //false ! Well done!
Salin selepas log masuk
System.out.println(src.getUser().equals(dist.getUser())); //true
Salin selepas log masuk
<br>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
好了,无论你的对象有多么的复杂,只要这些对象都能够实现java.lang.Serializable接口,就可以进行克隆,而且这种克隆的机制是JVM完成的,不需要修改实体类的代码,方便多了。
Atas ialah kandungan terperinci Java中对象克隆(Clone)的实例教程. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!