一、原型模式
原型模式就是從一個物件再建立另一個可定製的物件,而且不需要知道任何建立的細節。
二、基本的原型模式
這裡模擬簡歷的建立與複製來說明原型模式的應用。
class Resume implements Cloneable{ private String name; private String sex; private String age; private String timeArea; private String company; public Resume(String name){ this.name=name; } //設定個人資訊 public void setPersonalInfo(String sex,String age){ this.sex=sex; this.age=age; } //設定工作經歷 public void setWorkExperience(String timeArea,String company){ this.timeArea=timeArea; this.company=company; } //列印 public void display(){ System.out.println("name sex age:"+name+" "+sex+" "+age); System.out.println("work experience"+timeArea+" "+company); } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class PrototypePattern { public static void main(String[] args) throws CloneNotSupportedException{ Resume a = new Resume("a的簡歷"); a.setPersonalInfo("male", "18"); a.setWorkExperience("2017", "companya"); Resume b=(Resume) a.clone(); b.setWorkExperience("2016","companyb"); Resume c=(Resume) a.clone(); c.setWorkExperience("2019","companyc"); a.display(); b.display(); c.display(); } }
輸出結果:
三、原型模式中的淺複製與深複製
上面的Resume類通過實現Cloneable介面才能使用clone方法,進行物件的克隆。
Java中物件的建立通常是通過new來實現的,通過從堆中申請一塊與需要的物件型別對應的記憶體空間的大小,在呼叫構造方法返回物件給引用,
假如需要建立一批屬性值都相同的物件,或許可以通過new一批物件來實現,但是這樣的話,效率未免太低,並不划算。
那麼如果是這樣呢?
class PersonTest{ } public class Test { public static void main(String[] args) { PersonTest personTest=new PersonTest(); PersonTest personTest1=personTest; System.out.println(personTest); System.out.println(personTest1); } }
輸出結果:
可見personTest和personTest1的地址是一樣的,說明他們兩個其實是指向同一個物件的引用,在這個過程中並沒有進行物件的複製。
那如果是進行一次克隆呢?像這樣:
class Person implements Cloneable{ int id; String name; Education education; public Person(int id, String name,Education education) { this.id = id; this.name = name; this.education=education; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Education getEducation() { return education; } public void setEducation(Education education) { this.education = education; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class Education{ String theUniversity; String degree; public Education(String theUniversity, String degree) { this.theUniversity = theUniversity; this.degree = degree; } public String getTheUniversity() { return theUniversity; } public void setTheUniversity(String theUniversity) { this.theUniversity = theUniversity; } public String getDegree() { return degree; } public void setDegree(String degree) { this.degree = degree; } } public class ObjectClone { public static void main(String[] args) throws CloneNotSupportedException{ Education educationA=new Education("清華大學","學士"); Person personA=new Person(1,"小明",educationA); Person personB=(Person) personA.clone(); System.out.println(personA==personB); System.out.println(personA.id==personB.id); System.out.println(personA.name==personB.name); System.out.println(personA.education==personB.education); System.out.println(); System.out.println(personA); System.out.println(personB); System.out.println(personA.id); System.out.println(personB.id); System.out.println(personA.name); System.out.println(personB.name); System.out.println(personA.education); System.out.println(personB.education); } }
輸出結果:
可見克隆以後進行了物件的複製,personA與personB的地址是不同的,複製以後兩個物件的id值與name,education都相同,比較結果都為true,乍看上去這好像是預期中的現象。
但是,
name作為String物件,在進行“==”的比較時比較的是物件是否相同,可見這次複製過程中,name的值並沒有複製,
Education的值是一個物件,education的地址也相同,
所以這次物件複製後的情況,為什麼跟上面的例子只傳引用沒有什麼區別啊?
克隆的過程中有幾個問題需要進行思考,
id作為int型資料,int是基本資料型別,複製的過程當中是逐位複製的,
但是對於String型別與其他引用型別,這裡都只是將原物件的引用值拷貝給了新物件的相應欄位。
這種就是淺複製,clone()方法進行的就是淺拷貝,這是需要注意的問題。
那麼如果需要進行深拷貝,則需要將clone()方法進行覆蓋,並且在clone()方法內把原物件引用的其他物件也拷貝一份。
那麼被引用的物件也需要實現Cloneable介面,並且實現clone()方法。
class Person implements Cloneable{ int id; String name; Education education; public Person(int id, String name,Education education) { this.id = id; this.name = name; this.education=education; } @Override protected Object clone() throws CloneNotSupportedException { Person person=(Person) super.clone(); person.education=(Education) education.clone(); return person; } } class Education implements Cloneable{ String theUniversity; String degree; public Education(String theUniversity, String degree) { this.theUniversity = theUniversity; this.degree = degree; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class ObjectClone { public static void main(String[] args) throws CloneNotSupportedException{ Education educationA=new Education("清華大學","學士"); System.out.println(educationA); Person personA=new Person(1,"小明",educationA); Person personB=(Person) personA.clone(); System.out.println(personA==personB); System.out.println(personA.id==personB.id); System.out.println(personA.name==personB.name); System.out.println(personA.education==personB.education); System.out.println(); System.out.println(personA); System.out.println(personB); System.out.println(personA.id); System.out.println(personB.id); System.out.println(personA.name); System.out.println(personB.name); System.out.println(personA.education); System.out.println(personB.education); } }
輸出結果如下:
程式碼跟上方相比,有一點點改動,但是根據輸出結果來看,這次原物件中的某些欄位的所引用的物件也進行了拷貝了。
四、關於clone的總結
由此可見clone()只是淺拷貝,也就是說除非基本型別外,引用型別只是拷貝引用,不會複製物件。
如果需要進行深拷貝,則需要使原物件所引用的物件型別也要繼承Cloneable介面,並且實現clone()方法,同時要注意物件與物件之間存在的巢狀問題,避免拷貝後的兩個物件仍然因為引用的某個物件存在關係。
五、關於Cloneable介面
上面提到了Cloneable介面,再對這個東西多瞭解一點,下面這個就是Cloneable介面,可見當中沒有任何方法和屬性。
Cloneable介面與Serializable介面一樣,都是標記型的介面,
實現 Cloneable來表示該物件能被克隆,能使用Object.clone()方法。如果沒有實現 Cloneable的類物件呼叫clone()就會丟擲CloneNotSupportedException。
如果將上面最後一個例項程式碼中Education類實現的Cloneable介面去除,那麼程式執行時會出現異常,
這裡想思考的是標記型介面是如何起作用的呢?
標記介面是沒有任何方法和屬性的介面,僅僅表明實現它的類是屬於一個特定的型別,我們知道實現一個介面的類是可以來代表這個介面型別的。
通常建立標記介面的目的就是主要是:
建立一個公共的父介面,之後可以通過多型對其進行擴充套件,但是Java虛擬機器卻可以根據這個介面的型別選擇相應的事件處理方案,也就是對實現這個標記介面的物件進行這個處理方案。
關於標記介面的作用描述還是不夠清晰,待之後學習的再深入一點再繼續探究吧。