java 设计模式-创建模式之原型模式


java深拷贝和浅拷贝的区别(转载)

复制代码
Object 类的 clone方法执行特定的克隆操作。
首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。(注意:所有的数组都被视为实现接口 Cloneable)否则,此方法会创建此对象的类的一个新实例,并像通过分配,严格使用此对象相应字段的内容初始化该对象的所有字段;这些字段的内容没有被自我克隆。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。
Object 类本身不实现接口 Cloneable,所以在类为 Object的对象上调用 clone 方法将会导致在运行时抛出异常。

下面从三个复制的实例代码来看它们之间的区别,我们需建立Person类:
public class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void display() {
System.out.println("Name:" + name + "/tAge:" + age);
}
}

一、普通复制
即我们最容易想到的将一个对象赋值给另外一个对象。
public static void main(String[] args) {
Person p1=new Person("jack",20);
Person p2=p1;
p1.setAge(49);//简单复制
p2.display();
p1.display();
System.out.println(p1);
System.out.println(p2);
}

结果: Name:jack Age:49
Name:jack Age:49
net.pcedu.clone.Person@c17164
net.pcedu.clone.Person@c17164
说明p1和p2对象的是同一个引用,所以再改属性2个都是改变的。

二、浅拷贝
浅拷贝必需对Book类实现Cloneable接口的clone方法
public class Book implements Cloneable{
String bookName;
double price;
Person author;
public Book(String bn,double price,Person author){
bookName = bn;
this.price = price;
this.author = author;
}
public Object clone(){
Book b = null;
try{
b = (Book)super.clone();
}catch(CloneNotSupportedExceptione){
e.printStackTrace();
}
return b;
}
public void display(){
System.out.print(bookName + "/t" +price + "/t") ;
author.display();
}
}
publicstatic void main(Stringargs[]){
Book b1 = new Book("Java编程",30.50,new Person("张三",34));
Book b2 = (Book)b1.clone();
b2.price = 44.0;
b2.author.setAge(45);
b2.author.setName("李四");
b2.bookName = "Java开发";
b1.display();
b2.display();
}
结果:
Java编程 30.5 Name:李四 Age:45
Java开发 44.0 Name:李四 Age:45
说明p1和p2是不同的对象,但是p1对象的属性值也拷贝到p2中去了。

问题如下: 发现在改变b2的author对象属性时b1的author对象的属性也改变了,说明在浅拷贝中的author这个对象没有被完全拷贝,而是使用同一引用,这样就要使用深拷贝了。

三、深拷贝
为了解决如上问题,我们需要用到深拷贝,其实很简单在拷贝book对象的时候加入如下语句
b.author =(Person)author.clone(); //将Person对象进行拷贝,Person对象需进行了拷贝
在运行上面的main方法,结果如下:
Java编程30.5 Name:张三 Age:34
Java开发44.0 Name:李四 Age:45
说明p1和p2是不同的对象,但是p1对象的属性值也拷贝到p2中去了(含引用对象属性的拷贝)。

定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

类型:创建类模式

类图:

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

  • 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  • 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。

        原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

实现代码:

[java]  view plain copy
  1. class Prototype implements Cloneable { 
  2.     public Prototype clone(){ 
  3.         Prototype prototype = null
  4.         try
  5.             prototype = (Prototype)super.clone(); 
  6.         }catch(CloneNotSupportedException e){ 
  7.             e.printStackTrace(); 
  8.         } 
  9.         return prototype;  
  10.     } 
  11.  
  12. class ConcretePrototype extends Prototype{ 
  13.     public void show(){ 
  14.         System.out.println("原型模式实现类"); 
  15.     } 
  16.  
  17. public class Client { 
  18.     public static void main(String[] args){ 
  19.         ConcretePrototype cp = new ConcretePrototype(); 
  20.         for(int i=0; i< 10; i++){ 
  21.             ConcretePrototype clonecp = (ConcretePrototype)cp.clone(); 
  22.             clonecp.show(); 
  23.         } 
  24.     } 

原型模式的优点及适用场景

       使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

       使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

       因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

原型模式的注意事项

  • 使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
  • 深拷贝与浅拷贝。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。例如:

  1. public class Prototype implements Cloneable { 
  2.     private ArrayList list = new ArrayList(); 
  3.     public Prototype clone(){ 
  4.         Prototype prototype = null
  5.         try
  6.             prototype = (Prototype)super.clone(); 
  7.             prototype.list = (ArrayList) this.list.clone(); 
  8.         }catch(CloneNotSupportedException e){ 
  9.             e.printStackTrace(); 
  10.         } 
  11.         return prototype;  
  12.     } 

        由于ArrayList不是基本类型,所以成员变量list,不会被拷贝,需要我们自己实现深拷贝,幸运的是java提供的大部分的容器类都实现了Cloneable接口。所以实现深拷贝并不是特别困难。

PS:深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8中基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝。

原型模式:

1、定义:原型模式就是通过一个原型对象来表明要创建的对象类型,然后用复制这个对象的方法来创建更痛类型的对象。

2、原理:有两部分组成,抽象原型和具体原型。

3、使用时机:系统需要 创建吃的对象是动态加载的,而且产品具有一定层次时,可以考虑使用原型模式。

1>当要实例化的类是在运行时刻指定时,例如,通过动态装载;

2>或者为了避免创建一个与产品类层次平行的工厂类层次时;

3>或者当一个类的实例只能有几个不同状态组合中的一种时。

4>建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

4、效果:

1>可以再运行时刻增加和删除产品。

2>可以通过改变值来指定产品。

3>可以通过改变结构来指定新对象。

4>减少子类的构造

5>可以用类动态配置应用。

5、实现:

1>使用一个原型管理器

2>实现克隆操作(浅拷贝和深拷贝)

3>初始化克隆对象。

6、使用原型模式的意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

7、解决的问题:

比如有一个对象,在某一时刻该对象中已经包含了一些有效的值,此时可能会需要一个和该对象完全相同的新对象,并且此后对新对象的任何改动都不会影响到原来对象中的值,也就是说新对象与原来的对象是两个独立的对象,但新对象的初始值是由原来的对象确定的。

Clone:

赋值创建对象:

1>java中赋值创建对象是可以实现对象的重用的,但是新对象和原对象是同一个引用;如果修改其中的一个对象的值,则另外的一个对象也会发生改变。

2>使用clone方法会返回对象的一个拷贝,这样一来,如果修改一个对象的值,则另外的对象不会发生改变的。


原型模式UML图:


UML

请注意,在 这边Cloneable并非指Java中的java.lang.Cloneable,而是指支持原型复制 的对象,必须实作之公开协议。

 同的语言可能提供不同程 度支持之对象复制技术,以Java而言,java.lang.Object本身即定义有clone()方法,因此所有的对象 基本上 皆具自我复制之能力,不 过真正要让对象支持复制,则对象必须实作java.lang.Cloneable这个标示接口(Tag interface)。


原型模式浅拷贝与原型模式深度拷贝:

看看这样一个示例:有个类(DogClone)实现了Java的Cloneable接口,也实现了Object的clone()方法,它持有另一个没有实现Cloneable接口并且没有复写Object的clone()方法的引用(Dog)。如果Dog同时实现了clone()方法也实现了Cloneable接口,在对DogClone做科隆操作的时候会不会影响dog的值呢?

 

Java代码   收藏代码
  1. package org.bestupon.prototype.copy;  
  2. /** 
  3.  * 同实现了克隆方法的类和实现克隆方法的类做比较 
  4.  * @author bestupon 
  5.  * 浅拷贝Dog 
  6.  */  
  7. public class Dog {  
  8.     /** 
  9.      * 狗腿条数 
  10.      */  
  11.     public int legCounts;  
  12.   
  13.     public Dog(int legCounts) {  
  14.         this.legCounts = legCounts;  
  15.     }  
  16.     /** 
  17.      * 改变狗的腿数量 
  18.      */  
  19.     public void changeLegCounts(){  
  20.         this.legCounts *=2;  
  21.     }  
  22.     public String toString () {  
  23.         return Integer.toString(this.legCounts);  
  24.     }  
  25. }  

 

 

Java代码   收藏代码
  1. package org.bestupon.prototype.clone;  
  2. /** 
  3.  * 该类是没有实现克隆方法的类和实现克隆方法的类做比较 
  4.  * @author bestupon 
  5.  * 深度拷贝用例 
  6.  */  
  7. public class Dog implements Cloneable{  
  8.     /** 
  9.      * 狗腿条数 
  10.      */  
  11.     public int legCounts;  
  12.   
  13.     public Dog(int legCounts) {  
  14.         this.legCounts = legCounts;  
  15.     }  
  16.     /** 
  17.      * 改变狗的腿数量 
  18.      */  
  19.     public void changeLegCounts(){  
  20.         this.legCounts *=2;  
  21.     }  
  22.       
  23.     @Override  
  24.     public Dog clone() throws CloneNotSupportedException {  
  25.         return (Dog)super.clone();  
  26.     }  
  27.     public String toString () {  
  28.         return Integer.toString(this.legCounts);  
  29.     }  
  30. }  

 

 

Java代码   收藏代码
  1. package org.bestupon.prototype.copy;  
  2.   
  3. public class DogClone implements Cloneable {  
  4.     /** 
  5.      * 狗腿条数 
  6.      */  
  7.     public int legCounts;  
  8.     /** 
  9.      * 初始化一个狗 
  10.      */  
  11.     Dog dog = new Dog(4);  
  12.     @Override  
  13.     protected DogClone clone() throws CloneNotSupportedException {  
  14.         return (DogClone)super.clone();  
  15.     }  
  16.       
  17. }  

 

 

Java代码   收藏代码
  1. <span style="white-space: normal;"> <span style="white-space: pre;">package org.bestupon.prototype.copy;</span></span>  
  2. /** 浅拷贝 
  3.  * @author bestupon 
  4.  * 
  5.  */  
  6. public class Client {  
  7.     public static void main(String args []) throws CloneNotSupportedException {  
  8.         DogClone dogClone = new DogClone();  
  9.         dogClone.legCounts = 3;  
  10.         System.out.println("原来的克隆狗腿数量:"+dogClone.legCounts);  
  11.         System.out.println("原来的普通狗腿的数量:"+dogClone.dog);//Dog的toString方法返回的值。  
  12.           
  13.         DogClone dogClone1 = (DogClone)dogClone.clone();  
  14.         dogClone1.legCounts=2 ;  
  15.           
  16.          Dog dog = dogClone1.dog;  
  17.          dog.changeLegCounts();  
  18.          System.out.println("克隆后原来狗腿数量:"+dogClone.legCounts);  
  19.          /** 
  20.           * 出现的结果是:8 
  21.           * 原因很简单就是dog是一个引用,改变一个对象的话,会改变另一个对象。 
  22.           */  
  23.          System.out.println("克隆后原来普通狗的数量:"+ dogClone.dog);  
  24.          System.out.println("克隆后克隆狗腿的数量:"+ dogClone1.legCounts);  
  25.          /** 
  26.           *改变源:改变了自身dogClone.dog,影像了对象dogClone.dog 的值, 
  27.           */  
  28.          System.out.println("克隆后普通狗的数量:"+ dogClone1.dog);  
  29.           
  30.     }  
  31. }  
 

两次运行结果:


没有实现Cloneable接口和clone()方法结果:

 

Java代码   收藏代码
  1. 原来的克隆狗腿数量:3  
  2. 原来的普通狗腿的数量:4  
  3. 克隆后原来狗腿数量:3  
  4. 克隆后原来普通狗的数量:8  
  5. 克隆后克隆狗腿的数量:2  
  6. 克隆后普通狗的数量:8  
 

 

实现了Cloneable接口和clone()方法结果:

 

Java代码   收藏代码
  1. 原来的克隆狗腿数量:3  
  2. 原来的普通狗腿的数量:4  
  3. 克隆后原来狗腿数量:3  
  4. 克隆后原来普通狗的数量:4  
  5. 克隆后克隆狗腿的数量:2  
  6. 克隆后普通狗的数量:8  


 同样的客户端为什么会有不同的结果呢?这就是所谓的浅拷贝和深拷贝的为题.

 

分析:

浅拷贝: DogClone类中的legCounts属性被科隆了,因为对科隆后的对象进行修改时,没有改变原来对象的值,其原因是实现了接口和方法,而DogClone,Dog对象 dog没有被科隆,而在修改科隆后的对象时,会改变原来对象的值。

如果要是都实现了接口和方法,就实现了深度科隆,不会影响远对象的值。

 

应用:

 不是所有的对象都是能实现深度科隆的,例如:StringBuffer就没有重载Clone()方法,而且StringBuffer还是一个final类,所以其不能自动实现深度科隆。


在Spring中.深度科隆是应用广泛的。在实体模型上面有这样一个注解:@Component("lawsuitArchive") @scope("prototype"),表明在别的地方应用的时候,是引用其原型,同时Entity也要实现Cloneable接口和复写clone()方法。因为原型在初始化的时候就已经创建了一部分有价值的东西,减少容器的压力,而在别的地方,例如action中引用该对象的时候,直接可以使用@Resource(name="lawsuitArchive")引用对象,Spring会自动的装配好你想要的对象。

注:附件有直接可以运行的示例。点击下载


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值