Java中的深克隆和浅克隆

Java中跟克隆有关的两个类分别是Cloneable接口和Object类中的clone方法,通过两者的协作来实现克隆。Cloneable是个标志接口,内部不存在任何方法,若要具有克隆能力,实现Cloneable接口的类必须重写从Object继承来的clone方法,并调用Object的clone方法,重写后的方法应为public 的。

clone方法首先会判对象是否实现了Cloneable接口,若无则抛出CloneNotSupportedException, 最后会调用internalClone. intervalClone是一个native方法,一般来说native方法的执行效率高于非native方法。

当某个类要复写clone方法时,要继承Cloneable接口。通常的克隆对象都是通过super.clone()方法来克隆对象。

Object的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 <tt>x</tt>, 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 <tt>true</tt>, but these are not absolute requirements. 
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be <tt>true</tt>, this is not an absolute requirement. 
     * <p>
     * By convention, the returned object should be obtained by calling
     * <tt>super.clone</tt>.  If a class and all of its superclasses (except
     * <tt>Object</tt>) obey this convention, it will be the case that
     * <tt>x.clone().getClass() == x.getClass()</tt>.
     * <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 <tt>super.clone</tt> 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 <tt>super.clone</tt>
     * need to be modified.
     * <p>
     * The method <tt>clone</tt> for class <tt>Object</tt> performs a 
     * specific cloning operation. First, if the class of this object does 
     * not implement the interface <tt>Cloneable</tt>, then a 
     * <tt>CloneNotSupportedException</tt> is thrown. Note that all arrays 
     * are considered to implement the interface <tt>Cloneable</tt>. 
     * 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 <tt>Object</tt> does not itself implement the interface 
     * <tt>Cloneable</tt>, so calling the <tt>clone</tt> method on an object 
     * whose class is <tt>Object</tt> will result in throwing an
     * exception at run time.
     *
     * @return     a clone of this instance.
     * @exception  CloneNotSupportedException  if the object's class does not
     *               support the <code>Cloneable</code> interface. Subclasses
     *               that override the <code>clone</code> method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */
    protected native Object clone() throws CloneNotSupportedException;

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. 
 * <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
 * @version %I%, %G%
 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable { 
}

克隆就是复制一个对象的复本,若只需要复制对象的字段值(对于基本数据类型,如:int,long,float等,则复制值;对于复合数据类型仅复制该字段值,如数组变量则复制地址,对于对象变量则复制对象的reference。深克隆与浅克隆的区别体现在对引用类型的克隆上,浅克隆示例如下:

public class ShadowClone implements Cloneable {

	private int a; // 基本类型
	private int[] b; // 非基本类型

	// 重写Object.clone()方法,并把protected改为public
	@Override
	public Object clone() {
		ShadowClone sc = null;
		try {
			sc = (ShadowClone) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return sc;
	}

	public int getA() {
		return a;
	}

	public void setA(int a) {
		this.a = a;
	}

	public int[] getB() {
		return b;
    }

	public void setB(int[] b) {
		this.b = b;
    }
}

测试如下:

public static void main(String[] args) {
		ShadowClone c1 = new ShadowClone();
        //对c1赋值
        c1.setA(100) ;
        c1.setB(new int[]{1000}) ;
        
        System.out.println("克隆前c1:  a="+c1.getA()+" b="+c1.getB()[0]);
        //克隆出对象c2,并对c2的属性A,B,C进行修改
        ShadowClone c2 = (ShadowClone) c1.clone();
        //对c2进行修改
        c2.setA(50) ;
        int []a = c2.getB() ;
        a[0]=5 ;
        c2.setB(a);
        System.out.println("克隆后c1:  a="+c1.getA()+" b="+c1.getB()[0]);
        System.out.println("克隆后c2:  a="+c2.getA()+ " b[0]="+c2.getB()[0]);
	}

 输出如下:

可以看出在克隆后的C2中修改数组的值,C1也被改变。

分析如下:

以上可以看出,基本类型可以使用浅克隆,而对于引用类型,由于引用的是内容相同,所以改变c2实例对象中的属性就会影响到c1。所以引用类型需要使用深克隆。另外,在开发一个不可变类的时候,如果这个不可变类中成员有引用类型,则就需要通过深克隆来达到不可变的目的。

深克隆与浅克隆的区别在于对复合数据类型的复制。若对象中的某个字段为复合类型,在克隆对象的时候,需要为该字段重新创建一个对象。

示例代码如下:

public class DeepClone implements Cloneable{
	private int a; // 基本类型
	private int[] b; // 非基本类型

	// 重写Object.clone()方法,并把protected改为public
	@Override
	public Object clone() {
		DeepClone sc = null;
		try {
			sc = (DeepClone) super.clone();
			int[] t = sc.getB();
			int[] b1 = new int[t.length];
			for (int i = 0; i < b1.length; i++) {
				b1[i] = t[i];
			}
			sc.setB(b1);
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return sc;
	}

	public int getA() {
		return a;
	}

	public void setA(int a) {
		this.a = a;
	}

	public int[] getB() {
		return b;
	}

	public void setB(int[] b) {
		this.b = b;
	}
}

运行如下:

对象模型:

使用序列化来实现深克隆:

public static void main(String[] args) {
		Demo03 t = new Demo03();
		DeepClone dc1 = new DeepClone();
		// 对dc1赋值
		dc1.setA(100);
		dc1.setB(new int[] { 1000 });
		System.out.println("克隆前dc1: a=" + dc1.getA() + "b[0]=" + dc1.getB()[0]);
		DeepClone dc2 = (DeepClone) t.deepClone(dc1);
		// 对c2进行修改
		dc2.setA(50);
		int[] a = dc2.getB();
		a[0] = 500;
		System.out.println("克隆后dc1: a=" + dc1.getA() + "b[0]=" + dc1.getB()[0]);
		System.out.println("克隆后dc2: a=" + dc2.getA() + "b[0]=" + dc2.getB()[0]);
	}
	 // 用序列化与反序列化实现深克隆
    public Object deepClone(Object src){
        Object o = null;
        try{
            if (src != null){
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(src);
                oos.close();
                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bais);
                o = ois.readObject();
                ois.close();
            }
        } catch (IOException e){
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return o;
    }

运行如下:

可以看到,两个引用所指向的对象在堆中相互独立,互不干扰,这样就实现了深度克隆。

总结:

1、克隆方法用于创建对象的拷贝,为了使用clone方法,类必须实现java.lang.Cloneable接口重写protected方法clone,如果没有实现Clonebale接口会抛出CloneNotSupportedException.

2、在克隆java对象的时候不会调用构造器

3、java提供一种叫浅拷贝(shallow copy)的默认方式实现clone,创建好对象的副本后然后通过赋值拷贝内容,意味着如果你的类包含引用类型,那么原始对象和克隆都将指向相同的引用内容,这是很危险的,因为发生在可变的字段上任何改变将反应到他们所引用的共同内容上。为了避免这种情况,需要对引用的内容进行深度克隆。

4、按照约定,实例的克隆应该通过调用super.clone()获取,这样有助克隆对象的不变性。如:clone!=original和clone.getClass()==original.getClass(),尽管这些不是必须的

转自:https://www.cnblogs.com/wuyudong/p/4399180.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值