設(shè)計(jì)模式基礎(chǔ)知識(shí)補(bǔ)充
為什么私有化構(gòu)造函數(shù)后可以防止外部通過new singleton來創(chuàng)建新實(shí)例
1、使用new關(guān)鍵字時(shí),就是在執(zhí)行構(gòu)造方法創(chuàng)建對(duì)象
new關(guān)鍵字的工作流程
-
內(nèi)存分配
JVM 先在堆內(nèi)存中為對(duì)象分配空間,并初始化所有實(shí)例變量為默認(rèn)值(如int默認(rèn)為0,引用類型默認(rèn)為null)。
如果用無參構(gòu)造方法,對(duì)象的變量也會(huì)有默認(rèn)值 -
執(zhí)行構(gòu)造方法
調(diào)用類的構(gòu)造方法(如Singleton())來初始化對(duì)象的狀態(tài)(如果有初始化代碼)。- 如果構(gòu)造方法是
private的,外部代碼無法通過new調(diào)用它,從而阻止直接創(chuàng)建對(duì)象。
- 如果構(gòu)造方法是
-
返回對(duì)象引用
最終返回新創(chuàng)建對(duì)象的引用(內(nèi)存地址)。
單例模式中的關(guān)鍵點(diǎn)
在單例模式中:
private Singleton() {} // 私有構(gòu)造方法
外部代碼嘗試 new Singleton() 時(shí):
編譯器會(huì)直接報(bào)錯(cuò),因?yàn)樗接袠?gòu)造方法對(duì)外不可見,無法調(diào)用。
(此時(shí)連第一步的內(nèi)存分配都不會(huì)發(fā)生,因?yàn)檎Z法檢查階段就失敗了)
靜態(tài)變量是只加載一次嗎,什么時(shí)候才會(huì)加載靜態(tài)變量
靜態(tài)變量在以下情況下會(huì)被加載:
-
類首次主動(dòng)使用時(shí)(主動(dòng)引用):
- 創(chuàng)建類的實(shí)例(
new) - 訪問類的靜態(tài)變量或靜態(tài)方法
- 使用反射(
Class.forName()) - 初始化子類時(shí)(父類會(huì)先初始化)
- 作為程序入口的主類(包含main()的類)
- 創(chuàng)建類的實(shí)例(
-
不會(huì)觸發(fā)加載的情況(被動(dòng)引用):
- 通過子類引用父類的靜態(tài)變量(不會(huì)初始化子類)
- 定義類的數(shù)組(如
MyClass[] arr) - 引用類的常量(final static常量,在編譯期已優(yōu)化)
所以利用靜態(tài)變量只會(huì)加載一次的特性
單例模式中的應(yīng)用(單例模式的其中一種--餓漢式(靜態(tài)常量))
在單例模式中利用靜態(tài)變量的特性:
class Singleton {
// 靜態(tài)變量在類加載時(shí)初始化,且只執(zhí)行一次
private static final Singleton instance = new Singleton();
private Singleton() {} // 私有構(gòu)造
public static Singleton getInstance() {
return instance; // 總是返回同一實(shí)例
}
}
然后在psvm中執(zhí)行
這樣
Singleton.getInstance就能得到singleton對(duì)象,然后由于類的static變量只會(huì)執(zhí)行一次,后期繼續(xù)Singleton.getInstance,得到的對(duì)象只是同一個(gè)
這個(gè)類的靜態(tài)變量加載時(shí)候就是上面的訪問類的靜態(tài)變量或靜態(tài)方法生成的
這樣可以保證:
- 線程安全(由JVM類加載機(jī)制保證)
- 全局唯一性
- 延遲加載(直到首次調(diào)用getInstance()時(shí)才加載類)
原型模式中為什么要繼承clone接口再重寫clone的方法,直接寫一個(gè)clone方法不是更快嗎?
非常好的問題!這觸及了設(shè)計(jì)模式的核心思想之一:在接口和契約下編程,而不是在實(shí)現(xiàn)下編程。
直接寫一個(gè) clone() 方法確實(shí)更快,但從設(shè)計(jì)角度來說,實(shí)現(xiàn) Cloneable 接口并重寫 Object.clone() 是更優(yōu)秀、更標(biāo)準(zhǔn)、更安全的選擇。原因如下:
- 建立明確的契約(最重要的原因)
· “我是一個(gè)可克隆的對(duì)象”:實(shí)現(xiàn) Cloneable 接口的首要目的,是給類的使用者(其他程序員或未來的你)一個(gè)明確的信號(hào):“這個(gè)類設(shè)計(jì)時(shí)考慮了克隆功能,并且支持克隆”。這是一個(gè)標(biāo)識(shí),是一種約定。
· 沒有契約的后果:如果只是隨意地自己寫一個(gè) myClone() 方法,使用者如何知道你的類有這個(gè)功能?他們需要閱讀源代碼或文檔才能發(fā)現(xiàn)。而實(shí)現(xiàn)標(biāo)準(zhǔn)的 Cloneable 接口和 clone() 方法是 Java 生態(tài)中眾所周知的約定,使用者一看便知。
- 與Java內(nèi)置機(jī)制和第三方庫兼容
· Java標(biāo)準(zhǔn)庫的支持:許多 Java 內(nèi)置的工具、框架或第三方庫(如通過反射操作對(duì)象、序列化庫、Bean工具等)可能會(huì)識(shí)別 Cloneable 接口,并調(diào)用標(biāo)準(zhǔn)的 clone() 方法來創(chuàng)建對(duì)象副本。如果你使用自定義方法,這些機(jī)制將完全失效。
· 多態(tài)性:你可以編寫接收 Cloneable 接口參數(shù)的方法,然后對(duì)所有實(shí)現(xiàn)了該接口的不同類的對(duì)象調(diào)用 clone()。如果每個(gè)類都有自己的克隆方法名(如 cloneA(), copyB(), duplicateC()),你就無法編寫通用的克隆代碼。
// 基于接口和多態(tài)的通用克隆方法
public void processAndClone(Cloneable original) {
try {
Object copy = original.clone();
// ... 對(duì)copy進(jìn)行一些操作
} catch (CloneNotSupportedException e) {
// 處理異常
}
}
// 如果每個(gè)人都有自己的方法名,這就無法實(shí)現(xiàn)
public void processAndClone(MyObject original) {
MyObject copy = original.myClone(); // 只能針對(duì)MyObject類
}
- 規(guī)范方法簽名和行為
Object.clone() 方法已經(jīng)定義了一個(gè)標(biāo)準(zhǔn)的方法簽名:
protected native Object clone() throws CloneNotSupportedException;
重寫它時(shí),我們通常會(huì)將其改為 public,并返回具體的類型(協(xié)變返回類型,這是Java 5后的特性),這提供了更好的類型安全。
@Override
public MyPrototype clone() {
try {
return (MyPrototype) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 因?yàn)槲覀円呀?jīng)實(shí)現(xiàn)了Cloneable,所以不會(huì)發(fā)生
}
}
這樣做保證了所有類的克隆方法都具有相同的行為:返回一個(gè)當(dāng)前對(duì)象的副本。自定義方法名無法強(qiáng)制這種一致性。
- “保護(hù)傘”功能:Object.clone() 的本地實(shí)現(xiàn)
雖然 Object.clone() 方法有很多問題(如淺拷貝、笨拙的異常處理),但它提供了一個(gè)高效的、 native 級(jí)別的內(nèi)存二進(jìn)制拷貝作為基礎(chǔ)實(shí)現(xiàn)。我們通常調(diào)用 super.clone() 來利用這個(gè)基礎(chǔ)功能,然后再根據(jù)需要重寫它來實(shí)現(xiàn)深拷貝。
public class ComplexObject implements Cloneable {
private int id;
private String name;
private int[] data; // 引用類型
@Override
public ComplexObject clone() {
try {
ComplexObject clone = (ComplexObject) super.clone(); // 1. 調(diào)用Object.clone()進(jìn)行淺拷貝
clone.data = data.clone(); // 2. 手動(dòng)對(duì)引用類型進(jìn)行深拷貝
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
直接自己寫一個(gè)方法,你就失去了這個(gè)可以快速完成所有字段淺拷貝的“起點(diǎn)”。
直接寫一個(gè)Clone方法的缺點(diǎn)總結(jié):
- 無契約:使用者無法通過類型系統(tǒng)得知該對(duì)象可克隆。
- 不兼容:無法與期望標(biāo)準(zhǔn) clone() 方法的工具、框架或通用代碼協(xié)作。
- 不一致:方法名稱和行為在不同類之間可能不統(tǒng)一,降低代碼可讀性和可維護(hù)性。
- 重復(fù)造輪子:放棄了 Object.clone() 提供的現(xiàn)成的、高效的淺拷貝基礎(chǔ)實(shí)現(xiàn)。
結(jié)論
實(shí)現(xiàn) Cloneable 并重寫 Object.clone() 是一種“遵守契約”的設(shè)計(jì)思想。它犧牲了一點(diǎn)初期的“方便”,換來了代碼的清晰性、一致性、兼容性和可維護(hù)性。這正是設(shè)計(jì)模式所倡導(dǎo)的:通過遵循良好的約定和模式,來構(gòu)建更健壯、更靈活的軟件結(jié)構(gòu)。
所以,雖然直接寫一個(gè)方法“更快”,但從軟件工程的角度來看,遵循標(biāo)準(zhǔn)接口是“更好”的選擇。

浙公網(wǎng)安備 33010602011771號(hào)