2-4:privateのコンストラクタでインスタンス化不可能を強制する
ユーティリティクラス(static メソッドだけを持つクラス) や インスタンスを持つべきでないクラス に対する標準的なパターン。
要点
- 「インスタンスを作ってはいけない」クラス(ユーティリティクラスなど)は、
private なコンストラクタを持たせて外部からの new を禁止する。
- 更に安全にするためにコンストラクタ内で throw new AssertionError() をして、
誤って(あるいはリフレクション経由で)呼ばれても即座に失敗させるのが推奨パターン。
- クラスを final にしてサブクラス化を禁止するのも合わせて行う。
推奨パターン
/**
* ユーティリティクラスの例(インスタンス化禁止)
*/
public final class StringUtils {
// インスタンス化を防ぐための private コンストラクタ
private StringUtils() {
throw new AssertionError("インスタンス化は不要です");
}
public static boolean isEmpty(String s) {
return s == null || s.isEmpty();
}
// 他の static ヘルパーメソッド...
}
ポイント
- final にしてサブクラス化を防止(サブクラス化されるとそこからインスタンス化される可能性が出る)。
- private コンストラクタだけでは「誤って new されること」を防げる(コンパイル時/通常コード経由)。
- AssertionError を投げることで、リフレクションでコンストラクタを呼ばれた場合でも即座に失敗させる。
なぜ throw するのか?
- 単に private StringUtils() {} としておくだけでも通常の new は不可能だが、リフレクションで setAccessible(true) をした場合にコンストラクタが呼ばれてしまうことがある。コンストラクタ内で常に例外を投げることで、リフレクション経由の呼び出しでもインスタンス化を防げる(例外が発生してインスタンスは得られない)。
- AssertionError は「プログラミングエラー」を示す unchecked error としてよく使われる(Effective Java の慣習)。
リフレクションで呼んだ場合の挙動(例)
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionTest {
public static void main(String[] args) {
try {
Constructor<StringUtils> c = StringUtils.class.getDeclaredConstructor();
c.setAccessible(true);
StringUtils obj = c.newInstance(); // ここで InvocationTargetException が投げられる
} catch (InvocationTargetException e) {
System.out.println("原因: " + e.getCause()); // => AssertionError: インスタンス化は不要です
} catch (Exception e) {
e.printStackTrace();
}
}
}
newInstance() は InvocationTargetException を投げ、その getCause() が AssertionError になります(よってインスタンスは作れない)。
まとめ
-
ユーティリティクラスやインスタンスを持たせたくないクラスは、public final class + private コンストラクタ(かつその中で throw new AssertionError())が推奨。
-
これで通常の new、および setAccessible(true) 経由の呼び出しに対して堅牢に「インスタンス化不可」を保障できる(ただし非常に低レベルなフレームワーク的手段を使えば回避される可能性は理論上あるが、通常のアプリでは十分)。