选择语言
durumis AI 总结的文章
- 單例是一種只允許建立一個實例的類別,通常用於建立無狀態物件或唯一的系統元件。
- 建立單例的方式有:使用公用靜態成員作為 final 欄位的方式、提供靜態工廠方法的方式以及使用枚舉類型的 方式,其中使用枚舉類型的方式最為理想。
- 在序列化單例類別時,應實作 Serializable 介面,並將所有實例欄位宣告為 transient,以及重新定義 readResolve() 方法。
單例模式
單例模式的概念
單例模式是指只允許創建一個實例的類。單例模式的典型示例包括無狀態對象或唯一的系統 組件。但是,單例類很難測試,除非將其類型定義為接口,並將其實現定義為接口的實現。
創建單例模式的方法
使用 public static 成員作為 final 欄位的方式
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {
}
public void speak() {
System.out.println("elvis");
}
私有構造函數僅在初始化 Elvis 實例時調用一次,確保它是整個系統中唯一的實例。 但是,可以使用 AccessibleObject.setAccessible() 調用私有構造函數,這種 反射修改方法可以通過在創建第二個對象時拋出異常來阻止。
- 優點
- API 明確顯示該類是單例。
- 簡潔。
提供 public static 的靜態工廠方法的方式
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {
}
public static Elvis getInstance() {
return INSTANCE;
}
public void speak() {
System.out.println("elvis");
}
除了通過反射修改之外,這種方法也能夠確保在整個系統中只有一個實例。只不過將欄位改為私有, 並使用靜態工廠方法返回對象。
- 優點
- 可以在不更改 API 的情況下將其更改為非單例。
- 例如,靜態工廠方法可以返回每個線程的不同實例。
- 如果需要,可以更改為泛型單例工廠方法。
- 可以將靜態工廠的函數引用用作供應商。
- 例如,可以使用 Supplier<Elvis> 代替 Elvis::getInstance。
- 可以在不更改 API 的情況下將其更改為非單例。
如果不需要使用上述優點,最好使用第一種方法。
使用枚舉類型的方式
public enum Elvis {
INSTANCE;
public void speak() {
System.out.println("elvis");
}
最理想的方式是使用枚舉類型。與前兩種方法相比,它對反射攻擊更安全,代碼也更簡潔。此外, 如下所述,前兩種方法在序列化時需要添加額外的代碼。
但是,需要注意的是,要創建的單例可以繼承接口,但不能繼承類。
單例類序列化時需要注意的事項
如果要序列化使用前兩種方法創建的單例類,除了實現 Serializable 之外,還需要將所有 實例欄位聲明為 transient,並重寫 readResolve() 方法以提供它。
private Object readResolve throws ObjectStreamException {
return INSTANCE;