Java中Clone(深拷贝与浅拷贝)

Java中的深拷贝,浅拷贝是一个很重要的概念,之前也曾因为这个问题在实际一个需求中翻过车,今天就把这个拿出来说一下,以下是这篇博客的大纲

一.引入

二.深拷贝&浅拷贝

三.clone方法&Cloneable接口

四.需要注意的问题

五.总结



一.引入

在以前有过这种因为浅拷贝出来的对象,改变了这个浅拷贝对象的某些属性值,导致原对象的属性也改变。 这其中就涉及到了Java中的深拷贝与浅拷贝问题。

修改步骤是

1. 让该类实现java.lang.Cloneable接口; 
2. 重写(override)Object类的clone()方法。



二.深拷贝&浅拷贝

首先需要明白,深拷贝和浅拷贝都是针对一个已有对象的操作。

(1)浅克隆(shallow clone)

浅拷贝是指拷贝对象时仅仅拷贝对象本身和对象中的基本变量,而不拷贝对象包含的引用指向的对象。 

(2)深克隆(deep clone)

深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
举例区别一下:对象CompanyA中包含对TeamA的引用,TeamA中包含对EmployeeA的引用。浅拷贝CompanyA得到CompanyB,CompanyB中依然包含对TeamA的引用,TeamA中依然包含对EmployeeA的引用。深拷贝则是对浅拷贝的递归,深拷贝CompanyA得到CompanyB,CompanyB中包含对TeamB(TeamA的copy)的引用,TeamB中包含对EmployeeB(EmployeeA的copy)的引用。

下面画个图看起来可能会更直观一点



三.clone方法&Cloneable接口

Cloneable和Serializable一样都是标记型接口,它们内部都没有方法和属性,implements Cloneable表示该对象能被克隆,能使用Object.clone()方法。如果没有implements Cloneable的类调用Object.clone()方法就会抛出CloneNotSupportedException。

一个典型的调用clone()方法的示例如下

class CloneClass implements Cloneable{ 
 public int param; 
  @Override
 public Object clone(){ 
  CloneClass o = null; 
  try{ 
   o = (CloneClass)super.clone(); 
  }catch(CloneNotSupportedException e){ 
   e.printStackTrace(); 
  } 
  return o; 
 } 
}


四.需要注意的问题

1.多层拷贝,多层克隆问题:

深拷贝的时候如果需要深拷贝的对象里面的属性如果都是基本类型,直接使用上面的clone方法示例即可,但是如果需要进行深拷贝的对象的属性有其他的引用类型的时候。需要将其他的这些引用类型的属性也进行深拷贝,即还要重写这些引用类型的属性的clone方法,然后再需要深拷贝的类的clone方法中调用。下面举个栗子来示范一下:

package com.dahua.psis.client.common.params;

import java.util.Date;

/**
 * TODO
 *
 * @author    31777
 * @version   Ver 0.1
 * @since     Ver 0.1
 * @Date      2018年1月14日
 *
 */
public class Child implements Cloneable {

private String name;
private Date birth;//Date里面已经重写了clone()方法
private Double weight;

public Child() {
}

public Child(String name, Date birth, Double weight) {
	this.name = name;
	this.birth = birth;
	this.weight = weight;
}

/**
 * Return a copy of this object.
 */
@Override
public Child clone() {
	Child c = null;
	try {
		c = (Child) super.clone();
		//在Child中重写Child的clone方法
	} catch (CloneNotSupportedException e) {
		e.printStackTrace();
	}
return c;
}

.............getter setter methods

@Override
public String toString() {
	return "Child [name=" + name + ", birth=" + birth + ", weight=" + weight + "]";
}

public static void main(String[] args) {
	Child childA = new Child("aa", new Date(), 11.1);
	Child childB = childA.clone();
	childB.setName("zzzzz");
	System.out.println(childA.toString() + "---" + childB.toString());
}

}


public class Father implements Cloneable {

private String name;
private Integer age;
private Child child;

/**
 * Return a copy of this object.
 */
@Override
public Father clone() {
	Father f = null;
	try {
	f = (Father) super.clone();
	//1.在Child中重写Child的clone方法;2.在对应引用类型属性中重写clone方法并在此处进行引用
		f.child = f.getChild().clone();
	} catch (CloneNotSupportedException e) {
		e.printStackTrace();
	} catch (Exception e) {
		e.printStackTrace();
	}
return f;
}

public Father() {
}

public Father(String name, Integer age) {
this.name = name;
this.age = age;
}
.............getter setter methods

@Override
public String toString() {
return "Father [name=" + name + ", age=" + age + ", child=" + child.toString() + "]";
}

public static void main(String[] args) {
Child child = new Child("joe", new Date(), 11.3);
Father fatherA = new Father("dave", 29);
fatherA.setChild(child);
System.out.println(fatherA.toString());
Father fatherB = fatherA.clone();
fatherB.setAge(33);
System.out.println("只改变Father类的基本类型属性的值");
System.out.println(fatherB.toString() + "---" + fatherA.toString());
System.out.println("同时改变Father类的Child相关的属性的值");
fatherB.getChild().setName("NEW-NAME");
System.out.println(fatherB.toString() + "---" + fatherA.toString());
/* 
   **********************这里是只在Father中重写clone方法后打印的*****************************************
 Father [name=dave, age=29, children=Child [name=joe, birth=Sun Jan 14 13:19:33 CST 2018, weight=11.3]] 只改变Father类的基本类型属性的值 Father [name=dave, age=33, children=Child [name=joe, birth=Sun Jan 14 13:19:33 CST 2018, weight=11.3]]---Father [name=dave, age=29, children=Child [name=joe, birth=Sun Jan 14 13:19:33 CST 2018, weight=11.3]] 同时改变Father类的Child相关的属性的值 Father [name=dave, age=33, children=Child [name=NEW-NAME, birth=Sun Jan 14 13:19:33 CST 2018, weight=11.3]]---Father [name=dave, age=29, children=Child [name=NEW-NAME, birth=Sun Jan 14 13:19:33 CST 2018, weight=11.3]] *//******************这里是在两个类中都重写clone方法并且在Father的clone方法中还调用了Child属性的clone方法后打印的************** * Father [name=dave, age=29, child=Child [name=joe, birth=Sun Jan 14 13:34:17 CST 2018, weight=11.3]] 只改变Father类的基本类型属性的值 Father [name=dave, age=33, child=Child [name=joe, birth=Sun Jan 14 13:34:17 CST 2018, weight=11.3]]---Father [name=dave, age=29, child=Child [name=joe, birth=Sun Jan 14 13:34:17 CST 2018, weight=11.3]] 同时改变Father类的Child相关的属性的值 Father [name=dave, age=33, child=Child [name=NEW-NAME, birth=Sun Jan 14 13:34:17 CST 2018, weight=11.3]]---Father [name=dave, age=29, child=Child [name=joe, birth=Sun Jan 14 13:34:17 CST 2018, weight=11.3]] */}}


2.在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
如果一个类重写了Object的clone()方法,同时需要继承Cloneable接口(虽然这个接口没有定义clone()方法),否则在调用clone()时候会报错CloneNotSupportedException异常,也就是说Cloneable接口只是一个合法调用clone()的标识(mark-interface)。
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦,而且如果修改了类的结构还需要重修修改clone方法,这时我们可以用序列化的方式来实现对象的深拷贝。可以使用Serializable运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。如下给出一个工具类。

public class CloneUtils {  
    // 拷贝一个对象  
    @SuppressWarnings("unchecked")  
    public static <T extends Serializable>  T clone(T obj) {  
         // 拷贝产生的对象  
         T clonedObj = null;  
         try {  
           // 读取对象字节数据  
           ByteArrayOutputStream baos = new ByteArrayOutputStream();  
           ObjectOutputStream oos = new ObjectOutputStream(baos);  
           oos.writeObject(obj);  
           oos.close();  
           // 分配内存空间,写入原始对象,生成新对象  
           ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());  
           ObjectInputStream ois = new ObjectInputStream(bais);  
           //返回新对象,并做类型转换  
           clonedObj = (T)ois.readObject();  
           ois.close();  
        } catch (Exception e) {  
           e.printStackTrace();  
        }  
        return clonedObj;  
    }  
}
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。这样就比较麻烦了。
简单的做法,实现Apache Commons可以直接实现
• 深克隆/拷贝(deep clone/copy): 即使用Apache下的commons工具包中的SerializationUtils
• 浅克隆/拷贝(shallow clone/copy):BeanUtils
简单的克隆,也可以通过 copy-constructor (拷贝构造方法) 实现,详情参考 Effective Java 作者 Josh Bloch 的这篇访谈: Copy Constructor Versus Cloning 其中也谈到了 Cloneable 接口的很多缺点。 


五.总结
1.clone说明
如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作,除非对其他引用类型也重写clone并在上级clone方法内进行调用。
2.实现对象克隆有两种方式:
    1). 实现Cloneable接口并重写Object类中的clone()方法;
    2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
3.应用:
选择使用clone方法还是使用序列化工具类,其实查看源码说明

/**
     * <p>Deep clone an <code>Object</code> using serialization.</p>
     *
     * <p>This is many times slower than writing clone methods by hand
     * on all objects in your object graph. However, for complex object
     * graphs, or for those that don't support deep cloning this can
     * be a simple alternative implementation. Of course all the objects
     * must be <code>Serializable</code>.</p>
     *
     * @param object  the <code>Serializable</code> object to clone
     * @return the cloned object
     * @throws SerializationException (runtime) if the serialization fails
     */
   public static Object clone(Serializable object) {
       return deserialize(serialize(object));
    }
使用序列化的性能是不如自己手写clone方法的,对于大部分的实际引用场景,需要我们做深拷贝操作的对象的属性基本都是比较简单的,这个时候我建议还是直接手写clone方法,而对于过分复杂,属性有很多引用类型并且会经常做调整的可以使用SerializationUtils。
 


  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值