原型模式(Prototype).就是對象的引用指向另外一個對象即 Object newObject = oldObject;java
這種作法是至關於newObject仍是指向oldObject的地址,也就是說,兩者其實是同樣的,將來也是同樣的,隨便對哪一個對象進行更改,兩者都會保持一致,由於能夠把它們看作兩個相同的「指針」;另一種常見的作法是,從新建立一個對象,用new來實例化,這樣就建立了另一個對象,即向內存中再寫入了一個對象,雖然內容同樣,但地址不同,可是這種作法費力,若是對象比較複雜的話。ide
原型模式在這種需求下就誕生了,咱們知道Object乃一切對象的父類(超類),而且Object有一個原生的clone方法,可是該方法的調用必需要求類實現了Cloneable接口,雖然Cloneable接口只是一個擺設,裏面空空蕩蕩,姑且就當Cloneable接口是clone方法實現的一個標誌吧!咱們能夠建立一個類實現Cloneable便可,在覆寫clone方法便可完成該類的克隆了。測試
原型模式的代碼實現
下面寫一個Dog類,該類實現了Cloneable接口,而且覆寫了超類的clone方法,裏面使用了super.clone,表示調用超類的原生代碼便可。注意,Dog類有一個非基本數據類型的變量eye,下面會介紹這個點。this
淺複製
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; } @Override protected 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方法,下面咱們來進行一個測試:spa
測試代碼一:
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和==)?它們是否屬於一樣的類呢?最後一個問題是毋庸置疑的,確定是同一個類。prototype
運行結果指針
綠眼睛 綠眼睛 狗一 狗二 false false true
從運行結果中能夠看到,在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對象的地址。code
這種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; } @Override protected 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())); } }
運行測試結果:
-----------------深複製-------------- 紅眼睛 綠眼睛 狗一 狗二 false false true
咱們看到深度複製,兩者便「分道揚鑣」了,除了複製之初二者是同樣的以外,後續的任何變化都不會對彼此產生任何影響了。這就是深複製。
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。