가끔 단순히 정적 메서드와 정적 필드만을 포함하는 클래스를 만들고 싶을 때가 있습니다. 이는 객체 지향적 사고 방식과는 다소 거리가 있어서 종종 부적절하게 사용되기도 하지만, 특정 상황에서는 유용하게 사용될 수 있습니다.
예를 들어, java.lang.Math나 java.util.Arrays 같은 클래스는 기본 타입 값이나 배열 관련 메서드들을 모아놓는데 사용됩니다. 또한, java.util.Collections 처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(또는 팩토리 메서드)를 모아놓을 수 있습니다. 이는 자바 8부터 인터페이스에도 가능해졌습니다.
마지막으로, 이러한 방식은 final 클래스와 관련된 메서드들을 모아놓을 때 사용될 수 있습니다. final 클래스는 상속이 불가능하므로, 해당 클래스에 메서드를 추가하려면 별도의 클래스에 정적 메서드를 둘 수밖에 없습니다.
정적 멤버만을 담고 있는 유틸리티 클래스는 인스턴스로 생성되어 사용하기 위해 설계된 것이 아닙니다. 하지만, 클래스에 생성자를 명시적으로 선언하지 않으면 컴파일러가 자동으로 기본 생성자를 생성합니다. 이렇게 되면 매개변수가 없는 public 생성자가 생겨나고, 사용자는 이 생성자가 자동으로 생성된 것인지 아닌지 구분할 수 없게 됩니다. 실제로, 이러한 실수로 인해 의도치 않게 인스턴스화를 허용하는 클래스들이 공개 API에서도 종종 발견됩니다.
유틸리티 클래스를 추상 클래스로 선언하는 것만으로는 인스턴스화를 막을 수 없습니다. 이는 사용자가 하위 클래스를 생성하여 인스턴스화할 수 있기 때문이며, 이러한 방식은 사용자에게 클래스를 상속해서 사용하라는 잘못된 인상을 줄 수 있습니다.
하지만, 클래스를 인스턴스화하는 것을 막는 방법은 간단합니다. 컴파일러가 기본 생성자를 생성하는 경우는 사용자가 생성자를 명시적으로 선언하지 않았을 때뿐이므로, private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있습니다. 이렇게 하면 클래스 외부에서 생성자에 접근할 수 없으므로 클래스의 인스턴스화가 불가능해집니다.
public class UtilityClass {
// 기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지용).
private UtilityClass() {
throw new AssertionError();
}
... // 나머지 코드는 생략
}
명시적으로 private 생성자를 선언하면, 클래스 외부에서는 이를 접근할 수 없게 됩니다. 클래스 내부에서도 실수로 생성자를 호출하지 않도록, AssertionError를 던지는 코드를 추가할 수 있습니다. 이런 방식은 클래스가 어떤 환경에서도 인스턴스화되는 것을 방지합니다. 그러나 생성자는 존재하되 호출할 수 없는 이 방식은 직관적이지 않기 때문에, 해당 코드에는 설명하는 주석을 첨부하는 것이 좋습니다.
또한, 이 방식은 클래스를 상속할 수 없게 만드는 부가적인 효과가 있습니다. 모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출해야 하는데, 상위 클래스의 생성자가 private으로 선언되었다면, 하위 클래스에서는 그 생성자에 접근할 수 없게 됩니다. 이로 인해 상속이 불가능해집니다.
'Study > Effective Java[이펙티브 자바]' 카테고리의 다른 글
[아이템 7] 다 쓴 객체 참조를 해제하라 (0) | 2023.06.20 |
---|---|
[아이템 5] 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2023.06.14 |
[아이템 3] private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2023.06.07 |
[아이템 2] 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2023.06.07 |