首页 > 代码库 > Java原型模式之浅拷贝-深拷贝
Java原型模式之浅拷贝-深拷贝
一、是什么?
浅拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量只复制引用,不复制引用的对象
深拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量也进行引用对象的复制
内部机制:
(1)关于Object类的clone方法
默认实现为“浅拷贝”,重写Object类中的clone方法。Java中所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域是protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。
(2)关于Java.lang.Cloneable接口
在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
二、怎么用?
(一)浅拷贝
浅拷贝运用:如果你改变一个非常基本类型的值时,原对象的值不要求改变时就用浅拷贝。就是一直处于覆盖的状态。例如:
packagelc.clone.shadow; public classShadowClone implements Cloneable { // 基本类型 private int a; // 非基本类型 private String b; // 非基本类型 private int[] c; // 重写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 String getB() { return b; } public void setB(String b) { this.b = b; } public int[] getC() { return c; } public void setC(int[] c) { this.c = c; } } 测试类Test.java packagelc.clone.shadow; public class Test { public static void main(String[] args)throws CloneNotSupportedException { ShadowClone c1 = new ShadowClone(); //对c1赋值 c1.setA(100) ; c1.setB("clone1") ; c1.setC(new int[]{1000}) ; System.out.println("克隆前: c1.a="+c1.getA() ); System.out.println("克隆前: c1.b="+c1.getB() ); System.out.println("克隆前: c1.c[0]="+c1.getC()[0]); System.out.println("-----------") ; //克隆出对象c2,并对c2的属性A,B,C进行修改 ShadowClone c2 = (ShadowClone)c1.clone(); //对c2进行修改 c2.setA(50) ; c2.setB("clone2"); int []a = c2.getC() ; a[0]=500 ; c2.setC(a); System.out.println("克隆后: c1.a="+c1.getA() ); System.out.println("克隆后: c1.b="+c1.getB() ); System.out.println("克隆后: c1.c[0]="+c1.getC()[0]); System.out.println("---------------") ; System.out.println("克隆后: c2.a=" + c2.getA()); System.out.println("克隆后: c2.b=" + c2.getB()); System.out.println("克隆后: c2.c[0]=" + c2.getC()[0]); } } 结果: 克隆前: c1.a=100 克隆前: c1.b=clone1 克隆前: c1.c[0]=1000 ----------- 克隆后: c1.a=100 克隆后: c1.b=clone1 克隆后: c1.c[0]=500 --------------- 克隆后: c2.a=50 克隆后: c2.b=clone2 克隆后: c2.c[0]=500
<span style="font-size: 14pt; font-family: SimSun; background-color: rgb(255, 255, 255);"> </span>
问题出现了,我指修改了克隆后的对象c2.c的值,但c1.c的值也改变了,与c2的值相等.以下针对浅克隆得出结论:基本类型是可以被克隆的,但引用类型只是copy地址,并没有copy这个地址指向的对象的值,这使得两个地址指向同一值,修改其中一个,当然另一个也就变了。
由此可见,浅克隆只适合克隆基本类型,对于引用类型就不能实现克隆了。
(二)深拷贝
利用序列化实现深度拷贝:
把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深度克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。
被克隆对象.DeepClone.java
packagelc.clone.deep; importjava.io.Serializable; <span style="color:#cc0000;">//要实现深克隆必须实现Serializable接口</span> public classDeepClone implements Serializable { private int a; private String b; private int[] c; public int getA() { return a; } public void setA(int a) { this.a = a; } public String getB() { return b; } public void setB(String b) { this.b = b; } public int[] getC() { return c; } public void setC(int[] c) { this.c = c; } } 测试类Test.java packagelc.clone.deep; importjava.io.ByteArrayInputStream; importjava.io.ByteArrayOutputStream; importjava.io.IOException; importjava.io.ObjectInputStream; importjava.io.ObjectOutputStream; public class Test { public static void main(String[] args)throws CloneNotSupportedException { Test t = new Test(); DeepClone dc1 = new DeepClone(); // 对dc1赋值 dc1.setA(100); dc1.setB("clone1"); dc1.setC(new int[] { 1000 }); System.out.println("克隆前: dc1.a=" + dc1.getA()); System.out.println("克隆前: dc1.b=" + dc1.getB()); System.out.println("克隆前: dc1.c[0]=" + dc1.getC()[0]); System.out.println("-----------"); DeepClone dc2 = (DeepClone)t.deepClone(dc1); // 对c2进行修改 dc2.setA(50); dc2.setB("clone2"); int[] a = dc2.getC(); a[0] = 500; dc2.setC(a); System.out.println("克隆前: dc1.a=" + dc1.getA()); System.out.println("克隆前: dc1.b=" + dc1.getB()); System.out.println("克隆前: dc1.c[0]=" + dc1.getC()[0]); System.out.println("-----------"); System.out.println("克隆后: dc2.a=" + dc2.getA()); System.out.println("克隆后: dc2.b=" + dc2.getB()); System.out.println("克隆后: dc2.c[0]=" + dc2.getC()[0]); } <span style="color:#cc0000;"> //用序列化与反序列化实现深克隆</span> public Object deepClone(Object src) { Object o = null; try { if (src != null) { <span style="color:#cc0000;">// 将对象写到流里</span> ByteArrayOutputStream baos =new ByteArrayOutputStream(); ObjectOutputStream oos = newObjectOutputStream(baos); oos.writeObject(src); oos.close(); <span style="color:#cc0000;"> // 将对象从流里读出来</span> ByteArrayInputStream bais = newByteArrayInputStream(baos .toByteArray()); ObjectInputStream ois = newObjectInputStream(bais); o = ois.readObject(); ois.close(); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return o; } } 结果: 克隆前: dc1.a=100 克隆前: dc1.b=clone1 克隆前: dc1.c[0]=1000 ----------- 克隆前: dc1.a=100 克隆前: dc1.b=clone1 克隆前: dc1.c[0]=1000 ----------- 克隆后: dc2.a=50 克隆后: dc2.b=clone2 克隆后: dc2.c[0]=500<span style="font-size: 14pt; font-family: SimSun; background-color: rgb(255, 255, 255);"> </span>
深克隆后:修改dc1或者dc2,无论是基本类型还是引用类型,他们的值都不会随着一方改变另一方也改变。
这样做的前提就是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在复制过程之外。
浅拷贝显然比深拷贝更容易实现,因为Java语言的所有类都会继承一个clone()方法,而这个clone()方法所做的正式浅拷贝。
有一些对象,比如线程(Thread)对象或Socket对象,是不能简单复制或共享的。不管是使用浅拷贝还是深拷贝,只要涉及这样的间接对象,就必须把间接对象设成transient而不予复制;或者由程序自行创建出相当的同种对象,权且当做复制件使用。
三、对比
通过以上对浅拷贝和深拷贝的简介,估计在脑子中已经了解了大概,接下来就通过对比来彻底消除对它们的疑惑吧!
从上图中进行对比就可以明白其实质:浅拷贝指向的是同一个引用对象,而深拷贝指向的是两个完全一个样的引用对象。所以如果不想让引用对象跟着改变,就必须用深拷贝。如果只是单纯的值类型那么两者皆可以,那就在今后的项目中实践吧!
四、总结
综上所述无论是浅拷贝还是深拷贝只要解决了问题就是好的拷贝,所以在今后的实践中要体会它们的价值,并让每个的价值都发挥最大化。
Java原型模式之浅拷贝-深拷贝