您的位置:首页 > 编程语言 > Java开发

Java原型模式之浅拷贝-深拷贝

2017-06-17 19:31 323 查看

一、是什么?

         浅拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量仅仅复制引用,不复制引用的对象

         深拷贝:对值类型的成员变量进行值的复制,对引用类型的成员变量也进行引用对象的复制

         内部机制:

      (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,从而将之排除在复制过程之外。

  浅拷贝显然比深拷贝更easy实现。由于Java语言的全部类都会继承一个clone()方法。而这个clone()方法所做的正式浅拷贝。

  有一些对象。比方线程(Thread)对象或Socket对象。是不能简单复制或共享的。

无论是使用浅拷贝还是深拷贝,仅仅要涉及这种间接对象,就必须把间接对象设成transient而不予复制;或者由程序自行创建出相当的同种对象,权且当做复制件使用。

三、对照

         通过以上对浅拷贝和深拷贝的简单介绍,预计在脑子中已经了解了大概,接下来就通过对照来彻底消除对它们的疑惑吧!


         从上图中进行对照就能够明确事实上质:浅拷贝指向的是同一个引用对象。而深拷贝指向的是两个全然一个样的引用对象。所以假设不想让引用对象跟着改变,就必须用深拷贝。

假设仅仅是单纯的值类型那么两者皆能够,那就在今后的项目中实践吧。

四、总结

         综上所述不管是浅拷贝还是深拷贝仅仅要攻克了问题就是好的拷贝,所以在今后的实践中要体会它们的价值。并让每一个的价值都发挥最大化。


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: