在 Java 類設計中,private 構造函數是一個強有力的設計宣告:「此類禁止外部直接實例化」。這不僅是語法限制,更是架構意圖的體現——將對象創建控制權收回類內部,配合靜態工廠方法、單例模式、建造者模式等,實現更受控、更靈活的對象生命週期管理。本文以真實專案中的 HibernateSessionFactory 為案例,深度剖析私有構造函數的應用場景、常見誤區與最佳實踐。
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() { /* ... */ }
}
package zy.pro.td.plugin;
public class HibernatePlugin implements PlugIn {
private HibernateSessionFactory hsf; // 錯誤:嘗試持有實例
public HibernatePlugin() {
hsf = new HibernateSessionFactory(); // 編譯錯誤: private constructor
}
// ...
}
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) { /* ... */ }
}
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;
}
}
優勢:延遲初始化、線程安全、防止反射/序列化破壞 (需額外處理)。
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) 返回單例,效能更優。
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();
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,即使內部反射調用也會失敗,語義更清晰。
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");
}
實作 Serializable 並提供 readResolve():
private Object readResolve() { return getInstance(); } // 反序列化時返回單例
| 場景 | 是否私有構造 | 核心目的 | 配套機制 |
|---|---|---|---|
| 工具類 (全靜態) | ✅ 是 | 防止無意義實例化 | 無 |
| 單例 | ✅ 是 | 全局唯一實例 | 靜態工廠方法 + 雙重檢查鎖 |
| 不可變對象/值對象 | ✅ 是 | 強制使用 Builder/工廠 | Builder 模式、靜態工廠 |
| 枚舉類型 | ✅ 隱式私有 | 固定實例集合 | JVM 保證單例、防反射/序列化 |
| 普通實體類/DTO | ❌ 否 (public/package) | 需頻繁創建、框架反射實例化 | 公共無參構造 + setter |
HibernateSessionFactory 的教訓經典而深刻:私有構造函數不是阻礙,而是設計契約。它強迫使用者思考「如何正確獲取對象」,而非「如何創建對象」。下次看到 private Constructor,請問自己:這類想讓我用 getInstance()、valueOf()、Builder 還是直接呼叫靜態方法?順應設計意圖,代碼自然簡潔、健壯、易維護。