標籤:
原型模式簡介
原型模式實際上不算一種設計模式,應該說是一種技巧吧。當我們需要建立與已有對象一樣的對象時,我們通常可以有兩種容易想到的方法,一種是將已有對象指向另外一個重新建立的對象,如
//將old賦給newObject newObject=oldObject;
這種做法是相當於newObject還是指向oldObject的地址,也就是說,二者實際上是一樣的,未來也是一樣的,隨便對哪個對象變更,二者都會保持一致,因為可以把它們看做兩個相同的“指標”;另外一種常見的做法是,重新建立一個對象,用new來執行個體化,這樣就建立了另外一個對象,即向記憶體中再寫入了一個對象,雖然內容一樣,但地址不一樣,但是這種做法費力,如果對象比較複雜的話。
原型模式在這種需求下就誕生了,我們知道Object乃一切對象的父類(超類),並且Object有一個原生的clone方法,但是該方法的調用必須要求類實現了Cloneable介面,雖然Cloneable介面只是一個擺設,裡面空空蕩蕩,姑且就當Cloneable介面是clone方法實現的一個標誌吧!我們可以建立一個類實現Cloneable即可,在覆寫clone方法即可完成該類的複製了。
原型模式的代碼實現
下面寫一個Dog類,該類實現了Cloneable介面,並且覆寫了超類的clone方法,裡面使用了super.clone,表示調用超類的原生代碼即可。注意,Dog類有一個非基礎資料型別 (Elementary Data Type)的變數eye,下面會介紹這個點。
淺複製
package com.prototype;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class Dog implements Cloneable,Serializable {/** * */private static final long serialVersionUID = -2050795770781171788L;private String name;Eye eye;public Dog(Eye eye) {this.eye=eye;}public String getName() {return name;}public void setName(String name) {this.name=name;}@Overrideprotected Object clone() throws CloneNotSupportedException {Dog dog;dog=(Dog) super.clone();return dog;} }class Eye implements Serializable{/** * */private static final long serialVersionUID = -2723012171722328322L;String name;public Eye(String name) {this.name=name;}}
原型模式中的複製方法
在上面的原型模式代碼中,我們覆寫了clone方法,下面我們來進行一個測試:
測試代碼一:
package com.prototype;/** * @author zzw922cn * */public class Test1 {public static void main(String[] args) throws CloneNotSupportedException {Dog dog = new Dog(new Eye("紅眼睛"));dog.setName("狗一");Dog object1 = (Dog) dog.clone();object1.eye.name="綠眼睛";object1.setName("狗二");System.out.println(dog.eye.name);System.out.println(object1.eye.name);System.out.println(dog.getName());System.out.println(object1.getName());System.out.println(object1.equals(dog));System.out.println(object1==dog);System.out.println(object1.getClass().equals(dog.getClass()));}}
在上面的代碼中可以看到,object1是dog的複製對象,當我們複製完成以後,再對object1進行調用相關設定,改變其Eye類型的變數以及String類型的變數,會發生什麼呢?dog是否會發生變化呢?並且object1與dog是否一樣呢(equals和==)?它們是否屬於同樣的類呢?最後一個問題是毋庸置疑的,肯定是同一個類。
運行結果
綠眼睛綠眼睛狗一狗二falsefalsetrue
從運行結果中可以看到,在object1修改了eye對象以後,dog的eye對象的name也自動由紅眼睛變為綠眼睛,但是object1修改了String類型的name對象後,dog卻保持原有的name對象。這二者有什麼聯絡或區別嗎?聯絡是String和Eye都是非基礎資料型別 (Elementary Data Type),Java的八大基礎資料型別 (Elementary Data Type)有char,byte,int,short,long,float,double,boolean。區別是既然同屬非基礎資料型別 (Elementary Data Type),但是一個跟隨複製對象變化而變化,另外一個卻保持不變,這是很奇怪的。因為它是String類型,String是一個例外。因此,總結一下clone方法,我們得知複製對象的基礎資料型別 (Elementary Data Type)欄位是原有對象欄位的複製,但是非基本類型(String除外)並沒有複製,而是對原有對象的非基本類型的一個引用罷了,這種情況正如博文一開始中的newObject與oldObject兩者的關係。而String類型則是一個例外,它是對原有對象的一個複製,並非指向原有的String對象的地址。
這種clone一般稱為淺複製,即並沒有完全複製!
測試代碼二
下面來進行一次深複製,即完全複製。我使用流的方式來進行深度複製,即完全拷貝。
深複製
package com.prototype;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class Dog implements Cloneable,Serializable {/** * */private static final long serialVersionUID = -2050795770781171788L;private String name;Eye eye;public Dog(Eye eye) {this.eye=eye;}public String getName() {return name;}public void setName(String name) {this.name=name;}@Overrideprotected Object clone() throws CloneNotSupportedException {Dog dog;dog=(Dog) super.clone();return dog;}/* 深複製 */ public Object deepClone() throws IOException, ClassNotFoundException { /* 寫入當前對象的二進位流 */ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); /* 讀出二進位流產生的新對象 */ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } }class Eye implements Serializable{/** * */private static final long serialVersionUID = -2723012171722328322L;String name;public Eye(String name) {this.name=name;}}
接著寫一個類似的測試代碼
package com.prototype;import java.io.IOException;public class Test2 {/** * equal強調內容是否相同 * =強調地址是否相同 * @param args * @throws CloneNotSupportedException * @throws IOException * @throws ClassNotFoundException */public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {Dog dog = new Dog(new Eye("紅眼睛"));dog.setName("狗一");System.out.println("-----------------深複製--------------");Dog object2 = (Dog) dog.deepClone();object2.eye.name="綠眼睛";object2.setName("狗二");System.out.println(dog.eye.name);System.out.println(object2.eye.name);System.out.println(dog.getName());System.out.println(object2.getName());System.out.println(object2.equals(dog));System.out.println(object2==dog);System.out.println(object2.getClass().equals(dog.getClass()));}}
運行測試結果:
-----------------深複製--------------紅眼睛綠眼睛狗一狗二falsefalsetrue
我們看到深度複製,二者便“分道揚鑣”了,除了複製之初兩者是一樣的之外,後續的任何變化都不會對彼此產生任何影響了。這就是深複製。 equals與==的區別
前面我們看到複製對象與原始對象的equals和==都返回false,無論是淺複製還是深複製。那麼equals和==到底是什麼關係呢?
對於基礎資料型別 (Elementary Data Type),==比較的是它們的值。而對於非基本類型的對象,==比較是它們在記憶體中的地址,如之前的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。
Java設計模式之原型模式