原型模式
? 原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象
? 調(diào)用者不需要知道任何創(chuàng)建細(xì)節(jié),不調(diào)用構(gòu)造函數(shù)
? 其屬于一種創(chuàng)建型模式
通用類圖

優(yōu)點(diǎn)
- 性能好
- 是在內(nèi)存二進(jìn)制流的拷貝,比直接new一個(gè)對(duì)象性能好,而且循環(huán)體內(nèi)產(chǎn)生大量對(duì)象時(shí),可以更好地提現(xiàn)優(yōu)點(diǎn)
- 逃避構(gòu)造函數(shù)的約束
- 直接在內(nèi)存中拷貝構(gòu)造函數(shù)是不會(huì)執(zhí)行的
適用場(chǎng)景
- 類初始化消耗資源較多
- new 產(chǎn)生的一個(gè)對(duì)象需要非常繁瑣的過(guò)程(數(shù)據(jù)準(zhǔn)備、訪問(wèn)權(quán)限等)
- 省略了自己去get,set的過(guò)程
- 構(gòu)造函數(shù)比較復(fù)雜時(shí)
- 循環(huán)體中產(chǎn)生大量對(duì)象時(shí)
使用
- 通過(guò)一個(gè)特定的方法來(lái)拷貝對(duì)應(yīng)的對(duì)象
自己提供接口并且實(shí)現(xiàn)
使用JDK的clone方法
淺克隆
測(cè)試
@Data
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
public static void main(String[] args) {
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("zzy");
List<String> hobbies = new ArrayList<String>();
hobbies.add("music");
hobbies.add("article");
prototype.setHobbies(hobbies);
//拷貝原型對(duì)象
ConcretePrototype cloneType = prototype.clone();
cloneType.getHobbies().add("program");
System.out.println("原型對(duì)象:" + prototype);
System.out.println("克隆對(duì)象:" + cloneType);
System.out.println(prototype == cloneType);
System.out.println("原型對(duì)象的愛(ài)好:" + prototype.getHobbies());
System.out.println("克隆對(duì)象的愛(ài)好:" + cloneType.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());
}
原型對(duì)象:ConcretePrototype{age=18, name='zzy', hobbies=[music, article, program]}
克隆對(duì)象:ConcretePrototype{age=18, name='zzy', hobbies=[music, article, program]}
false
原型對(duì)象的愛(ài)好:[music, article, program]
克隆對(duì)象的愛(ài)好:[music, article, program]
true
通過(guò)結(jié)果可以看到通過(guò)clone拷貝出來(lái)對(duì)象的集合類型的內(nèi)存地址沒(méi)有改變
基本數(shù)據(jù)類型直接拷貝了,但是引用數(shù)據(jù)類型拷貝的是屬性的內(nèi)存地址,具體的元素并沒(méi)有拷貝
這種方式會(huì)給我們未來(lái)的使用帶來(lái)風(fēng)險(xiǎn)
深克隆
序列化和反序列化的方式
手寫流
這種方式對(duì)象一定要記得序列化
public ConcretePrototype deepClone(){
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (ConcretePrototype)ois.readObject();
}catch (Exception e){
e.printStackTrace();
return null;
}
- 缺點(diǎn)
- 性能不好
- 占用IO
- 沒(méi)有通過(guò)構(gòu)造方法來(lái)生成對(duì)象
- 會(huì)破壞單例模式
通過(guò)json序列化和反序列化
常用的工具
Spring 的BeanUtils
org.springframework.beans.BeanUtils#copyProperties
Apache的Beanutils
org.apache.commons.beanutils.BeanUtils
不推薦使用,性能差
對(duì)于對(duì)象拷貝加了很多的檢驗(yàn),包括類型的轉(zhuǎn)換,甚至還會(huì)檢驗(yàn)對(duì)象所屬的類的可訪問(wèn)性,非常復(fù)雜,這也造就了它的差勁的性能,
原型模式在源碼中的提現(xiàn)
ArrayList
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
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);
}
}
}
HashMap
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
public Object clone() {
HashMap<K,V> result;
try {
result = (HashMap<K,V>)super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.reinitialize();
result.putMapEntries(this, false);
return result;
}
}
注意事項(xiàng)
構(gòu)造函數(shù)不會(huì)被執(zhí)行
? 對(duì)象拷貝時(shí)構(gòu)造函數(shù)確實(shí)沒(méi)有被執(zhí)行,這點(diǎn)從原理來(lái)講也是可以講得通的,Object類的clone方法的原理是從內(nèi)存中(具體地說(shuō)就是堆內(nèi)存)以二進(jìn)制流的方式進(jìn)行拷貝,重新分配一個(gè)內(nèi)存塊,那構(gòu)造函數(shù)沒(méi)有被執(zhí)行也是非常正常的了。
深拷貝和淺拷貝
深拷貝和淺拷貝建議不要混合使用,特別是在涉及類的繼承 時(shí),父類有多個(gè)引用的情況就非常復(fù)雜,建議的方案是深拷貝和淺拷貝
final修飾的變量是不會(huì)被拷貝的
問(wèn)題
通過(guò)實(shí)現(xiàn)
Cloneable接口怎么實(shí)現(xiàn)克隆原理是什么?有什么問(wèn)題?代碼中是如何驗(yàn)證是深克隆還是淺克隆的?
一般都是直接基于內(nèi)存二進(jìn)制流來(lái)進(jìn)行拷貝,不會(huì)經(jīng)過(guò)構(gòu)造函數(shù),性能能夠提升很多。
- 注意
- 是淺拷貝的
- 引用數(shù)據(jù)類型是不會(huì)被拷貝的,拷貝的是內(nèi)存地址,不會(huì)創(chuàng)建一個(gè)新的
- final修飾的變量是不會(huì)被拷貝的
- 是淺拷貝的
- 驗(yàn)證方式
- 通過(guò)比較內(nèi)存地址來(lái)判斷
深克隆有哪些解決辦法?
- 通過(guò)序列化和反序列化
- 通過(guò)Json工具
如果我需要單例,怎么防止克隆破壞單例
- 重寫readResolve方法
我的筆記倉(cāng)庫(kù)地址gitee 快來(lái)給我點(diǎn)個(gè)Star吧