————— 第二天 —————
————————————
假如有一天,小灰被外星人抓走了,外星人要拿小灰做實驗,想了解小灰在吃得好、睡得好、玩得開心的場景下,與現(xiàn)實中小灰的生存狀態(tài)有什么區(qū)別。
于是,外星人克隆了幾個一模一樣的小灰:
就這樣,小灰的原型被留在現(xiàn)實中,而三個復制體分別提供了吃得好、睡得好、玩得開心三種不同環(huán)境,小灰的原型則不受三個復制體的影響。
過了一段時間,我們來觀察一下本體與分身的生存狀態(tài):
但需要注意的是,clone()方法并不是Cloneable接口里的,而是Object類里的,Cloneable是一個標識接口,標識這個類的對象是可被拷貝的,如果沒有實現(xiàn)Cloneable接口,卻調(diào)用了clone()方法,就會報錯。
// protected native Object clone() throws CloneNotSupportedException;protected Object clone() throws CloneNotSupportedException { if (!(this instanceof Cloneable)) { throw new CloneNotSupportedException( "Class " getClass().getName() " doesn't implement Cloneable"); } return internalClone(); } // Native helper method for cloning. private native Object internalClone(); |
Java中的數(shù)據(jù)類型,分為基本類型和引用類型。在一個方法里的變量如果是基本類型的話,變量就直接存儲在這個方法的棧幀里,例如int、long等;而引用類型則在棧幀里存儲這個變量的指針,指向堆中該實體的地址,例如String、Array等。深拷貝和淺拷貝是只針對引用數(shù)據(jù)類型的。
比如一個方法有一個基本類型參數(shù)和一個引用類型參數(shù),在方法體里對參數(shù)重新賦值,會影響傳入的引用類型參數(shù),而不會影響基本類型參數(shù),因為基本類型參數(shù)是值傳遞,而引用類型參數(shù)是引用傳遞。
先定義一個用戶類:
// 這是一個非常簡單的用戶類 public class User { private String name; private int age; public User(String name, int age) { this.name=name; this.age=age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{name='" name ", age=" age '}'; } } |
private int x=10; public void updateValue(int value){ value = 3 * value; } private User user= new User("大黃",20); public void updateUser(User student){ student.setName("小灰"); student.setAge(18); } public void test(){ System.out.println("調(diào)用前x的值:" x); updateValue(x); System.out.println("調(diào)用后x的值:" x); System.out.println("調(diào)用前user的值:" user.toString()); updateUser(user); System.out.println("調(diào)用后user的值:" user.toString()); } |
調(diào)用前x的值:10 調(diào)用后x的值:10 調(diào)用前user的值:User{name='大黃, age=20} 調(diào)用后user的值:User{name='小灰, age=18} |
傳遞基本類型的方法(updateValue())流程圖:
傳遞引用類型的方法(updateUser())流程圖:
這其中也包含著例外,比如String類型和大小不超過127的Long類型,雖然也是引用類型,卻像基本類型一樣不受影響。這是因為它們會先比較常量池維護的值,這涉及VM的內(nèi)容,今天不做過多討論。
淺拷貝是在按位(bit)拷貝對象,這個對象有著原始對象屬性值的一份精確拷貝。我們結(jié)合應用場景分析一下,還是剛才的User類,我們增加一個存放地址的內(nèi)部類Address,我們需要用戶信息可以被其他module查詢,但是不允許它們被其他module修改,新增代碼如下:
// 這是一個稍微復雜的、支持拷貝的用戶類 public class User implements Cloneable { // ……省略上文代碼…… private Address address; @NonNull @NotNull @Override public User clone() { try{ return (User)super.clone(); }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address{ // 地市 public String city; // 區(qū)縣 public String county; // 鄉(xiāng)鎮(zhèn)街道 public String street; } }
|
// 這是一個更復雜的、支持深拷貝的用戶類 public class User implements Cloneable { // ……省略上文代碼…… @NonNull @NotNull @Override public User clone() { try{ User newUser = (User)super.clone(); newUser.setName(this.name); newUser.setAddress(this.address.clone()); return newUser; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address implements Cloneable{ // ……省略上文代碼…… @NonNull @NotNull @Override public Address clone() { try{ Address newAddress = (Address)super.clone(); newAddress.city = this.city; newAddress.county = this.county; newAddress.street = this.street; return newAddress; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } } } |
需要注意的是,上面代碼的深拷貝其實并不徹底,因為徹底的深拷貝幾乎是不可能實現(xiàn)的,那樣不但可能存在引用關(guān)系非常復雜的情況,也可能存在引用鏈的某一級上引用了一個沒有實現(xiàn)Cloneable接口的第三方對象的情況。
絕大多數(shù)設(shè)計模式都是犧牲性能提升開發(fā)效率的,原型模式則是為數(shù)不多的犧牲開發(fā)效率提升性能的設(shè)計模式。
private User user= new User("大黃",20); public void testNew(){ User user1 = new User("小灰",18); } public void testClone(){ User user2 = user.clone(); } |
// access flags 0x1 public testNew()V ……省略…… MAXSTACK = 4 MAXLOCALS = 2 // access flags 0x1 public testClone()V ……省略…… MAXSTACK = 1 MAXLOCALS = 2 |
@Override public Object clone() { return new Intent(this); } |
最后我們來總結(jié)一下原型模式的核心用途:
1.解決構(gòu)建復雜對象的資源消耗問題,提升創(chuàng)建對象的效率。
2.保護性拷貝,防止外部對只讀對象進行需修改。