Java中深度克隆和浅度克隆
一:使用目的:
就是为了快速构造一个和已有对象相同的副本。如果需要克隆对象,一般需要先创建一个对象,然后将原对象中的数据导入到新创建的对象中去,而不用根据已有对象进行手动赋值操作。
二:Object中的clone()方法
protected native Object clone() throws CloneNotSupportedException;
说明:1.这是一个navtive方法 2.要使用该方法必须继承Object类,因为修饰符为protected 3.返回值为Object,需要强转
使用该方法时:x.clone()!=x为true,对于基础类型来说,在堆内存中创建了一个独立且内容与之相同的内存区域.对于引用数据类型来说,克隆对象和原始对象在java 堆(heap)中是两个独立的对 象,x.clone().getClass() == x.getClass() 他们所属的类是同一个,x.clone().equals(x) 所比较的对象内容相同
三:深度克隆和浅度克隆
浅度克隆:被克隆得到的对象基本类型的值修改了,原对象的值不会改变
深度克隆:被克隆得到的对象基本类型的值修改了,原对象的值改变
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;
}
}
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]=500 ;
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]);
console:
克隆前c1: a=100 b=1000
克隆前c1: a=100 b=500
克隆后c2: a=50 b[0]=500
可见:基本类型可以使用浅克隆,而对于引用类型,由于引用的是内容相同,所以改变c2实例对象中的属性就会影响到c1。所以引用类型需要使用深克隆。另外,在开发一个不可变类的时候,如果这个不可变类中成员有引用类型,则就需要通过深克隆来达到不可变的目的。
四:重写clone方法完成深度克隆
class bottle implements Cloneable { public wine wn; public bottle(wine wn) { this.wn = wn; } // 覆写clone()方法 protected Object clone() throws CloneNotSupportedException { bottle newBtl = (bottle) super.clone(); newBtl.wn = (wine) wn.clone(); return newBtl; } } class wine implements Cloneable { int degree; public int getDegree() { return degree; } public void setDegree(int degree) { this.degree = degree; } // 覆写clone()方法 protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
public static void main(String[] args) throws CloneNotSupportedException {
bottle bottle = new bottle(new wine());
bottle bottle1 = (bottle) bottle.clone();
System.out.println("bottle1.wine : " + bottle1.wn.getDegree() );
bottle1.wn.setDegree(100);
System.out.println("bottle1.wine : " + bottle1.wn.getDegree() );
System.out.println("bottle.wine : " + bottle.wn.getDegree());
}
console:
bottle1.wine : 0 bottle1.wine : 100 bottle.wine : 0
如果wine类中多了一个String name的属性呢?
class wine implements Cloneable { int degree; String name="法国白兰地"; public int getDegree() { return degree; } public void setDegree(int degree) { this.degree = degree; } public String getName() { return name; } public void setName(String name) { this.name = name; } // 覆写clone()方法 protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
class bottle implements Cloneable { public wine wn; public bottle(wine wn) { this.wn = wn; } // 覆写clone()方法 protected Object clone() throws CloneNotSupportedException { bottle newBtl = (bottle) super.clone(); newBtl.wn = (wine) wn.clone(); return newBtl; } }
Test
bottle bottle = new bottle(new wine()); bottle bottle1 = (bottle) bottle.clone(); System.out.println("bottle1.wine : " + bottle1.wn.getName() ); bottle1.wn.setName("中国二锅头"); System.out.println("bottle1.wine : " + bottle1.wn.getName() ); System.out.println("bottle.wine : " + bottle.wn.getName());
console:
bottle1.wine : 法国白兰地
bottle1.wine : 中国二锅头
bottle.wine : 法国白兰地
五:使用序列化实现深度克隆
public class DeepPerson implements Serializable { private int a; private int[] b; public DeepPerson() { } public DeepPerson(int a, int[] b) { this.a = a; this.b = b; } 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 class Test1 { public static void main(String[] args) throws CloneNotSupportedException{ DeepPerson dc1 = new DeepPerson(); // 对dc1赋值 dc1.setA(100); dc1.setB(new int[] { 1000 }); System.out.println("克隆前dc1: a=" + dc1.getA()+"b[0]=" + dc1.getB()[0]); DeepPerson dc2 = (DeepPerson) 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 static Object deepClone(Object object){ Object o=null; try{ if (object != null){ ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); 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; } }
console:
克隆前dc1: a=100b[0]=1000 克隆后dc1: a=100b[0]=1000 克隆后dc2: a=50b[0]=500
六、总结:
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(),尽管这些不是必须的