首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2SE

Java 私有構造函數深度解析:單例模式、工廠模式與 HibernateSessionFactory 實戰案例

     編輯:J2SE

前言:為什麼要把構造函數設為 private?

在 Java 類設計中,private 構造函數是一個強有力的設計宣告:「此類禁止外部直接實例化」。這不僅是語法限制,更是架構意圖的體現——將對象創建控制權收回類內部,配合靜態工廠方法、單例模式、建造者模式等,實現更受控、更靈活的對象生命週期管理。本文以真實專案中的 HibernateSessionFactory 為案例,深度剖析私有構造函數的應用場景、常見誤區與最佳實踐。

一、 案例還原:HibernateSessionFactory 編譯失敗

1.1 原始代碼 (簡化版)

package zy.pro.wd.util;

public class HibernateSessionFactory {
    private static final ThreadLocal threadLocal = new ThreadLocal<>();
    private static final Configuration cfg = new Configuration();
    private static SessionFactory sessionFactory;
    private static final String CONFIG_FILE = "/hibernate.cfg.xml";

    // 私有構造函數 - 關鍵點
    private HibernateSessionFactory() { }

    public static Session currentSession() throws HibernateException {
        Session session = threadLocal.get();
        if (session == null) {
            if (sessionFactory == null) {
                try {
                    cfg.configure(CONFIG_FILE);
                    sessionFactory = cfg.buildSessionFactory();
                } catch (Exception e) {
                    System.err.println("Error Creating SessionFactory");
                    e.printStackTrace();
                }
            }
            session = sessionFactory.openSession();
            threadLocal.set(session);
        }
        return session;
    }
    
    public static void closeSession() { /* ... */ }
}

1.2 調用方代碼 (錯誤示範)

package zy.pro.td.plugin;

public class HibernatePlugin implements PlugIn {
    private HibernateSessionFactory hsf; // 錯誤:嘗試持有實例
    
    public HibernatePlugin() {
        hsf = new HibernateSessionFactory(); // 編譯錯誤: private constructor
    }
    // ...
}

1.3 編譯錯誤訊息

HibernateSessionFactory() has private access in zy.pro.wd.util.HibernateSessionFactory

二、 核心誤區:把「工具類」當成「狀態類」用

HibernateSessionFactory 設計為無狀態工具類 (Utility Class):

  • 所有欄位 static,屬於類級別,非實例級別。
  • 所有方法 static,無需實例即可調用。
  • 私有構造函數防止實例化,強制使用者呼叫靜態方法 HibernateSessionFactory.currentSession()。

調用方 HibernatePlugin 錯誤地將其視為需實例化的 Bean,試圖 new HibernateSessionFactory() 並持有引用,違背了設計意圖。

三、 正確調用方式

public class HibernatePlugin implements PlugIn {
    // 無需持有 HibernateSessionFactory 實例
    // 無需建構子
    
    public void init(ActionServlet servlet, ModuleConfig config) {
        try {
            // 直接靜態調用,獲取當前線程綁定的 Session
            Session session = HibernateSessionFactory.currentSession();
            // 綁定到 JNDI 或執行初始化邏輯
            bindFactoryToJNDI(session);
        } catch (HibernateException e) {
            throw new ServletException(e);
        }
    }
    
    private void bindFactoryToJNDI(Session session) { /* ... */ }
}

四、 私有構造函數的四大經典應用模式

4.1 單例模式 —— 確保全局唯一實例

public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {} // 私有構造
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) instance = new Singleton(); // 雙重檢查鎖
            }
        }
        return instance;
    }
}

優勢:延遲初始化、線程安全、防止反射/序列化破壞 (需額外處理)。

4.2 靜態工廠方法 —— 語意豐富、可返回子類、可緩存

public final class Boolean {
    private Boolean() {} // 防止 new Boolean()
    
    public static Boolean valueOf(boolean b) {
        return b ? Boolean.TRUE : Boolean.FALSE; // 返回常量實例,避免創建新對象
    }
    
    public static Boolean valueOf(String s) {
        return "true".equalsIgnoreCase(s) ? Boolean.TRUE : Boolean.FALSE;
    }
}

對比構造函數:new Boolean(true) 每次創建新對象;Boolean.valueOf(true) 返回單例,效能更優。

4.3 建造者模式 —— 解決參數過多、不可變對象構建

public class HttpRequest {
    private final String url;
    private final String method;
    private final Map headers;
    private final byte[] body;
    private final int timeout;
    
    private HttpRequest(Builder b) { // 私有構造,僅 Builder 可調用
        this.url = b.url; this.method = b.method; 
        this.headers = b.headers; this.body = b.body; this.timeout = b.timeout;
    }
    
    public static class Builder {
        private String url; private String method = "GET";
        private Map headers = new HashMap<>();
        private byte[] body; private int timeout = 5000;
        
        public Builder url(String u) { url = u; return this; }
        public Builder method(String m) { method = m; return this; }
        public Builder header(String k, String v) { headers.put(k, v); return this; }
        public Builder body(byte[] b) { body = b; return this; }
        public Builder timeout(int t) { timeout = t; return this; }
        
        public HttpRequest build() { return new HttpRequest(this); }
    }
}

// 使用: 鏈式調用、參數可選、順序無關
HttpRequest req = new HttpRequest.Builder()
    .url("https://api.example.com")
    .method("POST")
    .header("Content-Type", "application/json")
    .body(jsonBytes)
    .build();

4.4 工具類/常量類 —— 純靜態成員,絕不實例化

public final class MathUtils {
    private MathUtils() { throw new AssertionError("No instances."); } // 防禦性編程
    
    public static int gcd(int a, int b) { /* ... */ }
    public static boolean isPrime(long n) { /* ... */ }
}

public final class Constants {
    private Constants() {} // 防止實例化
    public static final String DB_URL = "jdbc:mysql://...";
    public static final int MAX_RETRY = 3;
}

防禦性技巧:構造函數拋出 AssertionError,即使內部反射調用也會失敗,語義更清晰。

五、 進階話題:反射與序列化的「破壞」與防護

5.1 反射繞過 private 構造函數

Constructor c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true); // 暴力反射
Singleton s1 = (Singleton) c.newInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // false! 單例被破壞

防護:在構造函數中檢查實例是否已存在:

private Singleton() {
    if (instance != null) throw new IllegalStateException("Already instantiated");
}

5.2 序列化/反序列化產生新實例

實作 Serializable 並提供 readResolve():

private Object readResolve() { return getInstance(); } // 反序列化時返回單例

六、 總結:私有構造函數設計決策表

場景是否私有構造核心目的配套機制
工具類 (全靜態)✅ 是防止無意義實例化無
單例✅ 是全局唯一實例靜態工廠方法 + 雙重檢查鎖
不可變對象/值對象✅ 是強制使用 Builder/工廠Builder 模式、靜態工廠
枚舉類型✅ 隱式私有固定實例集合JVM 保證單例、防反射/序列化
普通實體類/DTO❌ 否 (public/package)需頻繁創建、框架反射實例化公共無參構造 + setter

結語

HibernateSessionFactory 的教訓經典而深刻:私有構造函數不是阻礙,而是設計契約。它強迫使用者思考「如何正確獲取對象」,而非「如何創建對象」。下次看到 private Constructor,請問自己:這類想讓我用 getInstance()、valueOf()、Builder 還是直接呼叫靜態方法?順應設計意圖,代碼自然簡潔、健壯、易維護。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved