前言
Effective Java 第 1 條建議:「考慮用靜態工廠方法取代建構子」。本文系統整理優缺點、標準命名慣例、六大實戰模式、Spring 整合技巧、效能影響與遷移清單,助你寫出更清晰、彈性、可測試的 API。
基本形式
public final class Boolean {
// 私有建構子,強制使用工廠
private Boolean(boolean value) { this.value = value; }
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE; // 回傳快取單例
}
}
五大優點
| 優點 | 說明 | 範例 |
| 1. 有名稱 | 建構子名固定為類別名,工廠可語意化 | BigInteger.probablePrime() vs new BigInteger() |
| 2. 不必每次建立新物件 | 回傳快取/單例/池化物件,減少 GC 壓力 | Boolean.valueOf(), Integer.valueOf(-128~127) |
| 3. 可回傳子類型 | 介面/抽象類別隱藏具體實作,支援多型 | Collections.emptyList() 回傳 EmptyList 子類 |
| 4. 參數化型別推導簡潔 | 鑽石運算子 <> 配合工廠,減少泛型囉嗦 | Map.of(), List.of() (JDK 9+) |
| 5. 服務提供者框架 (SPI) | JDBC DriverManager.getConnection()、ServiceLoader | 動態發現實作類別 |
兩大缺點
- 無法子類化:若建構子皆私有,子類別無法呼叫
super()。解法:提供 protected 建構子或 @VisibleForTesting 套件私有建構子。
- 難以被 IDE/框架發現:反序列化、DI 容器、建構器注入可能找不到。解法:
@JsonCreator、@Autowired 標註工廠、或保留一個公共建構子供框架用。
標準命名慣例 (Google/Java 標準)
| 慣例 | 用途 | 範例 |
from | 單一參數型別轉換 | Date.from(instant) |
of | 多參數聚合/值物件建立 | EnumSet.of(e1, e2), LocalDate.of(y,m,d) |
valueOf | from + of 綜合、型別轉換 | Integer.valueOf("123") |
getInstance | 單例/共享實例 | Calendar.getInstance() |
newInstance | 每次建立新實例 (如 Builder) | ArrayList.newInstance() |
getType | 回傳不同類型 (工廠在不同類別) | FileSystems.getDefault() |
newType | 建立新實例 (工廠在不同類別) | Files.newBufferedReader() |
六大實戰模式
1. 快取/池化 (Immutable 小物件)
public final class Color {
private static final Map CACHE = new ConcurrentHashMap<>();
private final int rgb;
private Color(int rgb) { this.rgb = rgb; }
public static Color of(int r, int g, int b) {
int key = (r << 16) | (g << 8) | b;
return CACHE.computeIfAbsent(key, Color::new);
}
}
2. 介面隱藏實作 (SPI 風格)
public interface ConnectionPool {
Connection borrow();
void release(Connection c);
static ConnectionPool create(String url, Props props) {
return switch (props.get("type")) {
case "hikari" -> new HikariPool(url, props);
case "dbcp" -> new DbcpPool(url, props);
default -> throw new IllegalArgumentException();
};
}
}
3. Builder 配合工廠 (複雜物件)
public final class HttpRequest {
private final String url, method, body;
private HttpRequest(Builder b) { this.url = b.url; this.method = b.method; this.body = b.body; }
public static Builder builder() { return new Builder(); }
public static class Builder {
private String url, method = "GET", body;
public Builder url(String u) { this.url = u; return this; }
public Builder method(String m) { this.method = m; return this; }
public Builder body(String b) { this.body = b; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
// 便利工廠
public static HttpRequest get(String url) { return builder().url(url).build(); }
public static HttpRequest post(String url, String body) { return builder().url(url).method("POST").body(body).build(); }
}
4. 參數驗證與失敗快速
public static User create(String email, String rawPassword) {
if (!EmailValidator.valid(email)) throw new IllegalArgumentException("Invalid email");
if (rawPassword.length() < 8) throw new IllegalArgumentException("Password too short");
return new User(email, PasswordEncoder.encode(rawPassword));
}
5. 多型回傳 (介面/抽象類別)
public interface List {
static List of() { return new ImmutableCollections.List0<>(); }
static List of(E e1) { return new ImmutableCollections.List1<>(e1); }
static List of(E e1, E e2) { return new ImmutableCollections.List2<>(e1, e2); }
// ... 實際回傳不同具體子類別
}
6. 可選依賴/條件建立
public static MetricsReporter create(Environment env) {
if (env.getProperty("datadog.enabled", Boolean.class, false)) {
return new DatadogReporter(env);
}
return new LoggingReporter(); // 預設無操作
}
Spring 整合最佳實踐
@Configuration
public class AppConfig {
// 靜態工廠方法建立 Bean
@Bean
public DataSource dataSource(DataSourceProperties props) {
return DataSourceBuilder.create()
.url(props.getUrl())
.username(props.getUsername())
.password(props.getPassword())
.type(HikariDataSource.class)
.build(); // 實際上 DataSourceBuilder 是 Builder 模式
}
// 實例工廠方法 (非靜態)
@Bean
public ObjectMapper objectMapper(JacksonProperties props) {
return JacksonConfig.createObjectMapper(props); // 靜態工廠
}
}
// 元件掃描時,建構子注入優先;若類別只有私有建構子 + 靜態工廠
// 需在 @Configuration 類別中手動 @Bean,或使用 @Component + 靜態工廠不支援自動裝配
@Component
public final class ExpensiveService {
private ExpensiveService() {} // 私有
public static ExpensiveService create(Config cfg) { return new ExpensiveService(); }
}
// 此時 Spring 無法自動 @Autowired ExpensiveService,需手動 @Bean
效能影響
- 逃逸分析:JIT 可將工廠方法內聯,消除物件分配 (標量替換),效能等同建構子。
- 快取命中:
Integer.valueOf、Boolean.valueOf 避免分配,顯著降低 GC。
- 反射開銷:框架若用反射呼叫工廠,比直接
new 慢;建議保留單一公共建構子供框架用,業務碼用工廠。
遷移清單 (建構子 → 靜態工廠)
- 將建構子改為
private 或 package-private。
- 新增
public static Type of(...) / valueOf(...)。
- 檢查所有
new Type(...) 呼叫點,改為 Type.of(...)。
- 若有子類別,提供
protected 建構子或 @VisibleForTesting。
- 為序列化/框架保留無參建構子或加
@JsonCreator 標註工廠。
- 更新 Javadoc,標註「請使用
of() 建立實例」。
- 執行完整測試套件,確認行為一致。