Java设计模式之原型模式

原型模式简介

原型模式实际上不算一种设计模式,应该说是一种技巧吧。当我们需要创建与已有对象一样的对象时,我们通常可以有两种容易想到的方法,一种是将已有对象指向另外一个重新创建的对象,如

1//将old赋给new 2Object newObject=oldObject;

这种做法是相当于newObject还是指向oldObject的地址,也就是说,二者实际上是一样的,未来也是一样的,随便对哪个对象进行更改,二者都会保持一致,因为可以把它们看做两个相同的“指针”;另外一种常见的做法是,重新创建一个对象,用new来实例化,这样就创建了另外一个对象,即向内存中再写入了一个对象,虽然内容一样,但地址不一样,但是这种做法费力,如果对象比较复杂的话。

原型模式在这种需求下就诞生了,我们知道Object乃一切对象的父类(超类),并且Object有一个原生的clone方法,但是该方法的调用必须要求类实现了Cloneable接口,虽然Cloneable接口只是一个摆设,里面空空荡荡,姑且就当Cloneable接口是clone方法实现的一个标志吧!我们可以创建一个类实现Cloneable即可,在覆写clone方法即可完成该类的克隆了。

原型模式的代码实现

下面写一个Dog类,该类实现了Cloneable接口,并且覆写了超类的clone方法,里面使用了super.clone,表示调用超类的原生代码即可。注意,Dog类有一个非基本数据类型的变量eye,下面会介绍这个点。

浅复制

1package com.prototype; 2 3import java.io.ByteArrayInputStream; 4import java.io.ByteArrayOutputStream; 5import java.io.IOException; 6import java.io.ObjectInputStream; 7import java.io.ObjectOutputStream; 8import java.io.Serializable; 9 10public class Dog implements Cloneable,Serializable { 11 12 /** 13 * 14 */ 15 private static final long serialVersionUID = -2050795770781171788L; 16 17 private String name; 18 19 Eye eye; 20 21 public Dog(Eye eye) { 22 this.eye=eye; 23 } 24 25 public String getName() { 26 return name; 27 } 28 29 public void setName(String name) { 30 this.name=name; 31 } 32 33 @Override 34 protected Object clone() throws CloneNotSupportedException { 35 Dog dog; 36 dog=(Dog) super.clone(); 37 return dog; 38 } 39} 40class Eye implements Serializable{ 41 /** 42 * 43 */ 44 private static final long serialVersionUID = -2723012171722328322L; 45 String name; 46 public Eye(String name) { 47 this.name=name; 48 } 49}

原型模式中的复制方法

在上面的原型模式代码中,我们覆写了clone方法,下面我们来进行一个测试:

测试代码一:

1package com.prototype; 2 3/** 4 * @author zzw922cn 5 * 6 */ 7public class Test1 { 8 9 public static void main(String[] args) throws CloneNotSupportedException { 10 Dog dog = new Dog(new Eye("红眼睛")); 11 dog.setName("狗一"); 12 13 Dog object1 = (Dog) dog.clone(); 14 15 object1.eye.name="绿眼睛"; 16 object1.setName("狗二"); 17 18 System.out.println(dog.eye.name); 19 System.out.println(object1.eye.name); 20 21 System.out.println(dog.getName()); 22 System.out.println(object1.getName()); 23 24 System.out.println(object1.equals(dog)); 25 System.out.println(object1==dog); 26 System.out.println(object1.getClass().equals(dog.getClass())); 27 } 28}

在上面的代码中可以看到,object1是dog的克隆对象,当我们克隆完成以后,再对object1进行调用相关设置,改变其Eye类型的变量以及String类型的变量,会发生什么呢?dog是否会发生变化呢?并且object1与dog是否一样呢(equals和==)?它们是否属于同样的类呢?最后一个问题是毋庸置疑的,肯定是同一个类。

运行结果

1绿眼睛 2绿眼睛 3狗一 4狗二 5false 6false 7true

从运行结果中可以看到,在object1修改了eye对象以后,dog的eye对象的name也自动由红眼睛变为绿眼睛,但是object1修改了String类型的name对象后,dog却保持原有的name对象。这二者有什么联系或区别吗?联系是String和Eye都是非基本数据类型,Java的八大基本数据类型有char,byte,int,short,long,float,double,boolean。区别是既然同属非基本数据类型,但是一个跟随克隆对象变化而变化,另外一个却保持不变,这是很奇怪的。因为它是String类型,String是一个例外。因此,总结一下clone方法,我们得知克隆对象的基本数据类型字段是原有对象字段的复制,但是非基本类型(String除外)并没有复制,而是对原有对象的非基本类型的一个引用罢了,这种情况正如博文一开始中的newObject与oldObject两者的关系。而String类型则是一个例外,它是对原有对象的一个复制,并非指向原有的String对象的地址。

这种clone一般称为浅复制,即并没有完全复制!

测试代码二

下面来进行一次深复制,即完全复制。我使用流的方式来进行深度复制,即完全拷贝。

深复制

1package com.prototype; 2 3import java.io.ByteArrayInputStream; 4import java.io.ByteArrayOutputStream; 5import java.io.IOException; 6import java.io.ObjectInputStream; 7import java.io.ObjectOutputStream; 8import java.io.Serializable; 9 10public class Dog implements Cloneable,Serializable { 11 12 /** 13 * 14 */ 15 private static final long serialVersionUID = -2050795770781171788L; 16 17 private String name; 18 19 Eye eye; 20 21 public Dog(Eye eye) { 22 this.eye=eye; 23 } 24 25 public String getName() { 26 return name; 27 } 28 29 public void setName(String name) { 30 this.name=name; 31 } 32 33 @Override 34 protected Object clone() throws CloneNotSupportedException { 35 Dog dog; 36 dog=(Dog) super.clone(); 37 return dog; 38 } 39 40 /* 深复制 */ 41 public Object deepClone() throws IOException, ClassNotFoundException { 42 43 /* 写入当前对象的二进制流 */ 44 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 45 ObjectOutputStream oos = new ObjectOutputStream(bos); 46 oos.writeObject(this); 47 48 /* 读出二进制流产生的新对象 */ 49 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); 50 ObjectInputStream ois = new ObjectInputStream(bis); 51 return ois.readObject(); 52 } 53 54} 55class Eye implements Serializable{ 56 /** 57 * 58 */ 59 private static final long serialVersionUID = -2723012171722328322L; 60 String name; 61 public Eye(String name) { 62 this.name=name; 63 } 64}

接着写一个类似的测试代码

1package com.prototype; 2 3import java.io.IOException; 4 5public class Test2 { 6/** 7 * equal强调内容是否相同 8 * =强调地址是否相同 9 * @param args 10 * @throws CloneNotSupportedException 11 * @throws IOException 12 * @throws ClassNotFoundException 13 */ 14 public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException { 15 Dog dog = new Dog(new Eye("红眼睛")); 16 dog.setName("狗一"); 17 18 System.out.println("-----------------深复制--------------"); 19 Dog object2 = (Dog) dog.deepClone(); 20 object2.eye.name="绿眼睛"; 21 object2.setName("狗二"); 22 23 System.out.println(dog.eye.name); 24 System.out.println(object2.eye.name); 25 26 System.out.println(dog.getName()); 27 System.out.println(object2.getName()); 28 29 System.out.println(object2.equals(dog)); 30 System.out.println(object2==dog); 31 System.out.println(object2.getClass().equals(dog.getClass())); 32 33 34 } 35}

运行测试结果:

1-----------------深复制-------------- 2红眼睛 3绿眼睛 4狗一 5狗二 6false 7false 8true

我们看到深度复制,二者便“分道扬镳”了,除了复制之初两者是一样的之外,后续的任何变化都不会对彼此产生任何影响了。这就是深复制。

equals与==的区别

前面我们看到克隆对象与原始对象的equals和==都返回false,无论是浅复制还是深复制。那么equals和==到底是什么关系呢?

对于基本数据类型,==比较的是它们的值。而对于非基本类型的对象,==比较是它们在内存中的地址,如之前的oldObject与newObject二者的地址相同;而equals方法,如果它没有被子类覆写,它最原始的也是比较对象在内存中的地址,如果被子类覆写了,就不好说了。例如在String类型中,equals方法比较的是字符串的“表面值”,它并不是比较对象在内存中的地址,而==比较的是两个字符串在内存中的地址是否一样。例如String str1="Java",String str2=new String("Java");那么二者的关系是str1!=str2,str1.equals(str2)=true。原因是str1存在于字符串常量池中,str2存在于Java堆中,二者地址当然不同;但是二者的表面值是一样的,都是Java,因此equals方法返回true。

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

笑说设计模式-小白逃课被点名

关于我简介工厂模式(FactoryPattern)是最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,而是通过使用一个共同的接口来指向新创建的对象。分类工厂模式可以分为三种,其中简单工厂一般不被认为是一种设计模式,可以将其看成是工厂方法的一种特殊

「超全超细」Java设计模式图文详解!!!

java设计模式—原型模式Java原型模式1、概述  啥是原型模式?  原型模式属于设计模式中的创建型中的一员,  原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象!  说大白话就是自己复制自己,通过原生对象复制出一个新的对象,这两个对象结构相同且相似;  需要注意的是,原型对象自己不仅是个对象还是个工厂!并且通过克隆方式创

JavaScript面向对象编程的15种设计模式

在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”。在JavaScript中并没有类这种概念,面向对象编程不是基于类,而是基于原型去面向对象编程,JS中的函数属于一等对象,而基于JS中闭包与弱类型等特性,在实现一些设计模式的方式上与众不同。ps:本文之讲述面向对象编程的设计模式策略,JavaScript原型的基础请参考阮一峰面向

Java中23种设计模式详解

Java中23种设计模式1\.设计模式31.1创建型模式41.1.1工厂方法41.1.2抽象工厂61.1.3建造者模式101.1.4单态模式131.1.5原型模式151.2结构型模式171.2.1适配器模式171.2.2桥接模式191.2.3组合