详解 Java 中的对象克隆

1,638 阅读6分钟
原文链接: yifeng.studio

前言

在 Java 语言中,我们说两个对象是否相等通常有两层含义:

  • 对象的内容是否相等,通常使用到对象的 equals(Object o) 函数;

  • 引用的地址是否相同,使用运算符 == 比较即可。

当两个对象通过赋值符号 = 赋值时,表明这两个对象指向了内存中同一个地址,所以改变其中一个对象的内容,也就间接地改变了另一个对象的内容。有时候,我们需要从一个已经存在的对象重新拷贝一份出来,并且不仅这两个对象内容相等,在内存中存在两个独立的存储地址,互不影响,这时,就需要用到 Java 中的克隆机制。

Cloneable

通过 Cloneable 接口可以很轻松地实现 Java 对象的克隆,只需要 implements Cloneable 并实现 Object 的 clone() 方法即可,如:

public class User implements Cloneable{

	private String username;
	
	private String password;

	public User(String username, String password) {
		super();
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	@Override
	public boolean equals(Object obj) {
		User user = (User) obj;
		if (username.equals(user.username) && password.equals(user.password)) {
			return true;
		}
		return false;
	}
	
}

注意这里对象实现的是 Object 类的 clone() 方法,因为 Cloneable 是一个空接口:

package java.lang;

/**
 * A class implements the Cloneable 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.
 * 

* Invoking Object's clone method on an instance that does not implement the * Cloneable interface results in the exception * CloneNotSupportedException being thrown. *

* By convention, classes that implement this interface should override * Object.clone (which is protected) with a public method. * See {@link java.lang.Object#clone()} for details on overriding this * method. *

* Note that this interface does not contain the clone 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 { }

从源码注释中可以看出,需要实现 Object 类中的 clone() 方法(注意:clone() 函数是一个 native 方法,同时抛出了一个异常):


/**
 * 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:
 * 
 * 
 * x.clone() != x
 * will be true, and that the expression:
 * 
 * 
 * x.clone().getClass() == x.getClass()
 * will be {@code true}, but these are not absolute requirements.
 * While it is typically the case that:
 * 
 * 
 * x.clone().equals(x)
 * will be {@code true}, this is not an absolute requirement.
 * 

* 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()}. *

* 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. *

* 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. *

* 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;

测试结果显示,通过 clone() 函数,我们成功地从 userOne 对象中克隆出了一份独立的 userThree 对象。

浅克隆与深克隆

谈此之前,我们先看一个例子,定义一个名为 Company 的类,并添加一个类型为 User 的成员变量:

public class Company implements Cloneable{

	private User user;
	
	private String address;

	public Company(User user, String address) {
		super();
		this.user = user;
		this.address = address;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	@Override
	public boolean equals(Object obj) {
		Company company = (Company) obj;
		if (user.equals(company.getUser()) && address.equals(company.address)) {
			return true;
		}
		return false;
	}
	
}

测试代码及测试结果如下:

public static void main(String[] args) throws CloneNotSupportedException{
	Company companyOne, companyTwo, companyThree;
	companyOne = new Company(new User("username", "password"), "上海市");
	companyTwo = companyOne;
	companyThree = (Company) companyOne.clone();
	
	System.out.println(companyTwo==companyOne);				//true
	System.out.println(companyTwo.equals(companyOne));		//true
	
	System.out.println(companyThree==companyOne);			//false
	System.out.println(companyThree.equals(companyOne));	//true
	
	System.out.println(companyThree.getUser()==companyOne.getUser());			//true ? 这里为什么不是false呢
	System.out.println(companyThree.getUser().equals(companyOne.getUser()));	//true
	
}

问题来了,companyThree 与 companyOne 中的 User 是同一个对象!也就是说 companyThree 只是克隆了 companyOne 的基本数据类型的数据,而对于引用类型的数据没有进行深度的克隆。也就是俗称的浅克隆。

浅克隆:顾名思义,就是很表层的克隆,只克隆对象自身的引用地址;

深克隆:也称“N层克隆”,克隆对象自身以及对象所包含的引用类型对象的引用地址。

这里需要注意的是,对于基本数据类型(primitive)和使用常量池方式创建的String 类型,都会针对原值克隆,所以不存在引用地址一说。当然不包括他们对应的包装类。

所以使用深克隆就可以解决上述 Company 对象克隆过后两个 user 对象的引用地址相同的问题。我们修改一下 Company 类的 clone() 函数:

@Override
protected Object clone() throws CloneNotSupportedException {
	Company company = (Company) super.clone();
	company.user = (User) company.getUser().clone();
	return company;
}

再运行测试代码,就能得到 companyThree.getUser()==companyOne.getUser() 为 false 的结果了。

Serializable实现

通过上述介绍,我们知道,实现一个对象的克隆,需要如下几步:

  1. 对象所在的类实现 Cloneable 接口;

  2. 重写 clone() 函数,如果包涵引用类型的成员变量,需要使用深克隆。

如果对象不包含引用类型成员或者数量少的话,使用 Cloneable 接口还能接受,但当对象包含多个引用类型的成员,同时这些成员又包含了引用类型的成员,那层层克隆岂不是相当繁琐,并且维护不便?所以,这里介绍一种更加方便的实现方式,使用 ObjectOutputStreamObjectOutputStream 来实现对象的序列化和反序列化:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public abstract class BeanUtils {
	@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;
	}
}

只要要克隆的对象以及对象所包含的引用类型的成员对象所在的类实现了 java.io.Serializable 接口即可实现完美克隆。