設計模式,不管是在準備面試過程,還是在實際工作編碼中,都是應該掌握的技能,所以特別整理一下設計模式相關(guān)的知識,希望對大家有所幫助
可能我們會遇到這樣一種場景:在編碼中有這樣一個對象,此對象比較重(構(gòu)造比較耗時)或者是構(gòu)造方法相當復雜,使用起來很麻煩,總之創(chuàng)建成本很高,類似這樣
public class HeavyObject {
public HeavyObject(){
try {
// 模擬次對象創(chuàng)建耗時
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("HeavyObject init success");
}
}
而這種對象可能由于業(yè)務的需求,需要在循環(huán)中創(chuàng)建并使用
public class PrototypeTest {
public static void main(String[] args) {
long l1 = System.currentTimeMillis();
for(int i = 0; i < 10; i ++) {
HeavyObject heavyObject = new HeavyObject();
System.out.println(heavyObject);
}
long l2 = System.currentTimeMillis();
System.out.println(l1 - l2);
}
}
上面這段代碼的效率相信不用執(zhí)行大家也知道是多大慘不忍睹,執(zhí)行測試,在我機器上為:10006ms;而針對這種情況能不能有一個好的方式來優(yōu)化呢?當然是今天的主角:原型模式啦。雖然號稱是“設計模式”,但是原型模式在實現(xiàn)的角度看確實很簡單:實現(xiàn)Cloneable接口即可,然后再獲取新的對象實例的時候使用原實例對象的clone()即可;
HeavyObject 變化為
public class HeavyObject implements Cloneable {
public HeavyObject(){
try {
// 模擬次對象創(chuàng)建耗時
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("HeavyObject init success");
}
}
測試方法
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
long l1 = System.currentTimeMillis();
HeavyObject heavyObject = new HeavyObject();
for(int i = 0; i < 10; i ++) {
// 通過clone獲取新的對象
HeavyObject cloneHeavyObject = (HeavyObject) heavyObject.clone();
System.out.println(cloneHeavyObject);
}
long l2 = System.currentTimeMillis();
System.out.println(l1 - l2);
}
}
此時測試結(jié)果為1000ms,耗時主要在為初次構(gòu)造HeavyObject 實例時構(gòu)造方法耗費,而且結(jié)果中,只打印了一次HeavyObject 構(gòu)造方法中的"HeavyObject init success"可以印證這一點,可見使用clone創(chuàng)建對象實例時沒有調(diào)用構(gòu)造方法,實際上是通過二進制流的方式只能讀取源數(shù)據(jù)并生成新的對象(Object中的native方法);
原型模式創(chuàng)建實例的方式看起來簡單而美好,但是有時候也是有些小坑的。比如我們的HeavyObject 中有List成員變量
public class HeavyObject implements Cloneable {
List list;
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
// ... 省略重復代碼
而我們使用的業(yè)務場景是這樣的
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
HeavyObject heavyObject = new HeavyObject();
List list = new ArrayList();
list.add(1);
heavyObject.setList(list);
// 通過clone拷貝對象
HeavyObject cloneHeavyObject = (HeavyObject) heavyObject.clone();
// 模擬業(yè)務場景,對clone的對象中的list進行操作
cloneHeavyObject.getList().remove(0);
System.out.println(heavyObject.getList());
}
執(zhí)行結(jié)果
HeavyObject init success
[]
Process finished with exit code 0
可見,對clone對象成員變量進行操作,影響了原對象。我們在最后一行打斷點,看一下

上面的原對象和下面的clone對象的成員變量list,指向的對象都是ArrayList@455,所以造成操作clone對象的List而影響了原對象,這種拷貝對象的方式,也被成為淺拷貝,淺拷貝就是將原對象直接拷貝一份,原對象中的成員變量的指針當然也拷貝過來,所以該例中的兩個對象的list仍舊指向同一個對象;怎么解決呢?其實jdk早給給我們了答案,我們翻看一下ArrayList,發(fā)現(xiàn)它也實現(xiàn)了Cloneable接口的,然后看下其clone()
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
ArrayList答題實現(xiàn)思路是將ArrayList本身拷貝后,將其成員變量elementData ,通過Arrays方法重寫拷貝一份賦值,我們嘗試把HeavyObject的clone()這么改,即新創(chuàng)建一個List對象
@Override
protected Object clone() throws CloneNotSupportedException {
HeavyObject cloneHeavyObject = (HeavyObject) super.clone();
cloneHeavyObject.list = new ArrayList(this.list);
return cloneHeavyObject;
}
在執(zhí)行測試方法結(jié)果
HeavyObject init success
[1]
Process finished with exit code 0
上面這種,除了拷貝對象本身以為,還將對象中其他對象也拷貝一份新的對象,也就是我們常說的深拷貝。其實上例實現(xiàn)的深拷貝,也是有問題的,比如List承載的元素依舊是引用類型,而我的拷貝方式是直接將原List中的對象拿過來加到新的List中,所以雖然List對象是新創(chuàng)建的,但其中元素依舊指向同一個對象,所以業(yè)務有需求時應繼續(xù)給List中對象也繼續(xù)拷貝一份新的。