static method와 static field만을 담은 클래스 생성을 남발하는 것은 Item3에서 언급한 단점들로 인해 객체 지향적 관점에서 보면 달갑지 않다. (다형성과 상속을 막고, 전역으로 데이터를 관리하게 되기 때문.)
하지만 분명 그 나름대로의 쓰임이 있다.
예시로 java.lang.Math, java.util.Arrays처럼 기본 타입 값이나 배열 관련 메서드들을 모아놓거나, java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩터리)들을 모아놓을 수도 있으며, final 클래스와 관련된 메서드들을 모아놓을 때도 사용한다.
public class Arrays {
private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;
// Suppresses default constructor, ensuring non-instantiability.
private Arrays() {}
static final class NaturalOrder implements Comparator<Object> {
@SuppressWarnings("unchecked")
public int compare(Object first, Object second) {
return ((Comparable<Object>)first).compareTo(second);
}
static final NaturalOrder INSTANCE = new NaturalOrder();
}
...
}
public final class Math {
/**
* Don't let anyone instantiate this class.
*/
private Math() {}
public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;
public static double sin(double a) {
return StrictMath.sin(a); // default impl. delegates to StrictMath
}
}
(private 생성자를 만들어두고 주석으로 경고 표시를 해두었음에 주목하자.)
하지만 이 방식을 사용할 때 주의해야할 점이 있는데, 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만든다는 것이다.
클라이언트는 해당 API가 생성자가 자동 생성된 것인지 모를 뿐 아니라, 더 나아가 상속을 받아서 사용하는 의도로 잘못 해석할 수 있는 여지를 주게 된다.
📌 인스턴스화 막기
해결책은 단순하다.
컴파일러는 기본 생성자가 없을 때 자동 생성하는 것이므로, 기본 생성자를 private 접근제어자로 미리 작성해두면 된다.
public class UtilityClass {
// 기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지용).
private UtilityClass() {
throw new AssertionError();
}
...
}
에러를 던질 필요는 없지만, 실수로라도 생성자를 호출하지 않도록 방지할 수 있다.
또한, 클라이언트 입장에선 생성자가 존재하는데 호출이 불가능 하다는 점에서 직관적으로 보이지 않을 수 있으므로 주석을 달아 알려주는 것이 좋다.
이 방식을 사용하면 상속을 불가능하도록 하는 효과도 있다.
하위 클래스에서 상위 클래스 생성자를 호출해야 하지만, private로 막아버렸으니 접근할 길을 막아두었기 때문이다.