2-1:コンストラクタの代わりにstaticファクトリメソッドを検討する
■staticメソッドとは何か
クラス内でpublic staticなメソッドを用意し、コンストラクタを読んでインスタンスを返すやり方。
newを直接使う代わりに"X.of(...)"、"X.valueOf(...)"、"X.getInstance(...)"のようなメソッドでインスタンスを取得する。
■メリット
①名前を付けられる
コンストラクタはクラス名と同じで意味が分かりにくいが、静的メソッドは from, of, valueOf, getInstance, newInstance, parse 等で用途を明示できる(例:直交座標 vs 極座標)。
②コンストラクタとは異なり、その呼び出し毎に新たなオブジェクトを生成する必要がないこと
例)Boolean.valueOf(true) は既存のインスタンスを返す。
重いオブジェクトや単一インスタンスを共有したい場合に有利。
③メソッドの戻り値型の任意のサブタイプのオブジェクトを返せること
戻り値の型(例えばインターフェースや抽象クラス)を公開しておいて、内部では用途に応じて適切な具象クラスを返せるということ。
④返されるオブジェクトのクラスは入力パラメータの値に応じて呼び出し事に変えられる
③とほぼ同じ
⑤返されるオブジェクトのクラスは、そのstaticファクトリメソッドを含むクラスが書かれた時点で存在する必要さえないこと
つまりクライアント側からは、staticファクトリメソッドを呼び出すだけなので、呼び出される具象クラスを作るのを先延ばしにできるということ
■デメリット
①new が使えない分、API上での発見性が下がる
利用者が「インスタンスをどう作るのか?」を直感で見つけにくい。良い Javadoc とメソッド名で補う必要あり。
②サブクラス化がやや制限されるケースがある
クラスがprivateコンストラクタで static factory のみを提供すると継承ができなくなる(これは意図されたメリットでもあるが、拡張性が必要なら注意)。
③バイナリ互換性 / シリアライズの取り扱い
シリアライズと組み合わせる場合は readResolve などの設計の注意が必要(コンストラクタ呼び出しと異なる振る舞いにならないように)。
■使い分けの目安(いつstaticファクトリを選ぶか)
・名前があると分かりやすくなるとき(複数の生成方法がある場合)。
・インスタンスの共有やキャッシュをしたいとき(シングルトン、値オブジェクト等)。
・実装を隠蔽してインターフェースを返したいとき。
・ジェネリクスの型推論を活かしたいとき。
・将来返す具体型を変える可能性があるとき。
※逆に、サブクラス化を公開API設計の一部にしたい場合や、discoverability(発見性)を重視するシンプルなケースはコンストラクタのままでも良い。
■命名規則
・of(...) — コンパクトに値をまとめて生成(Java 9 の List.of など)。
・valueOf(...) — 既存のインスタンスを返す可能性があることを示唆(プリミティブラッパ等)。
・getInstance(...) / getType(...) — シングルトンやキャッシュを返す場合に適合。
・newInstance(...) — 常に新しいインスタンスを返すことを期待させる(注意して使う)。
・from(...) / of / parse(...) — 入力の性質(文字列から構築など)に応じて使い分け。
■まとめ
「コンストラクタだけに頼らず、静的ファクトリメソッドを設計選択肢として持て」という設計の勧め。
可読性、再利用性、実装の隠蔽、型推論など多くの利点があり、特にライブラリや公開APIを設計する際に強力な武器になる。
ただし発見性や継承設計などの点では注意が必要。
サンプルコード
/**
* Effective Java 2章:オブジェクトの生成と消滅
* 項目1 コンストラクタの代わりにstaticファクトリメソッドを検討する。
*
* 典型パターン
*/
public final class Complex {
private final double re, im;
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
// 静的ファクトリメソッド(名前がつけれる)
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}
// 異なる生成方法を別名で提供できる
public static Complex fromPoler(double r, double theta) {
return new Complex(r * Math.cos(theta), r * Math.sin(theta));
}
}
/**
* Effective Java 2章:オブジェクトの生成と消滅
* 項目1 コンストラクタの代わりにstaticファクトリメソッドを検討する。
*
* メリット②の例
*/
public final class Boolean {
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
private final boolean value;
private Boolean(boolean value) {
this.value = value;
}
public static Boolean valueOf(boolean b) {
return b ? TRUE : FALSE; // 既存のオブジェクトを返す
}
}
/**
* Effective Java 2章:オブジェクトの生成と消滅
* 項目1 コンストラクタの代わりにstaticファクトリメソッドを検討する。
*
* メリット③の例
*/
public final class MyLists {
private MyLists() {
}
public static <T> List<T> of(T... elems) {
switch (elems.length) {
case 0:
return Collections.emptyList(); // 共有インスタンス
case 1:
return Collections.singletonList(elems[0]); // 軽量実装
default:
return new ArrayList<>(Arrays.asList(elems)); // 可変で効率的な実装
}
}
}