Item4 - 인스턴스화를 막으려거든 private 생성자를 사용하라

2023. 11. 15. 23:16Book/이펙티브 자바

 

객체 생성과 파괴

 

!아이템4 목표!

사용자가 정적 멤버만 담은 유틸리티 클래스의 인스턴스를 만들어서 정적 메서드를 사용할 수 있다. 이는 문법적으로 이상하지 않지만 매우 불필요하고 사용자가 이 메서드가 인스턴스 메서드인지 static 한지 알 수 없기 때문에 좋지 않은 코드이기도 하다 이번 목표는 애초에 생성자를 이용해 인스턴스화 되는 것을 막는것이다.

 

 

들어가기 전 요약

 - 정적 메서드만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 클래스가 아니다.

 - 추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다.

 - private 생성자를 추가하며 클래스의 인스터스화를 막을 수 있다.

 - 생상자에 주석으로 인스턴스화 불가한 이유를 설명하는 것이 좋다.

 - 상속을 방지할 때도 같은 방법을 사용할 수 있다.

 

 

이따금 정적 메서드와 정적 필드만을 담은 클래스를 만들고 싶을 때가 있다. 객체 지향적이지 않은 방법이지만, 나름 쓰임새가 있다.

 ex) 기본 타입 값을 모아놓은 java.lang.Math

public final class Math {

    /**
     * Don't let anyone instantiate this class.
     */
    private Math() {}

    /**
     * The {@code double} value that is closer than any other to
     * <i>e</i>, the base of the natural logarithms.
     */
    public static final double E = 2.7182818284590452354;

    /**
     * The {@code double} value that is closer than any other to
     * <i>pi</i>, the ratio of the circumference of a circle to its
     * diameter.
     */
    public static final double PI = 3.14159265358979323846;

    /**
     * Constant by which to multiply an angular value in degrees to obtain an
     * angular value in radians.
     */
    private static final double DEGREES_TO_RADIANS = 0.017453292519943295;

    /**
     * Constant by which to multiply an angular value in radians to obtain an
     * angular value in degrees.
     */
    private static final double RADIANS_TO_DEGREES = 57.29577951308232;
    
    .
    .
    .

 

 

ex) 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드

public class Collections {
    // Suppresses default constructor, ensuring non-instantiability.
    private Collections() {
    }

    // Algorithms

    private static final int BINARYSEARCH_THRESHOLD   = 5000;
    private static final int REVERSE_THRESHOLD        =   18;
    private static final int SHUFFLE_THRESHOLD        =    5;
    private static final int FILL_THRESHOLD           =   25;
    private static final int ROTATE_THRESHOLD         =  100;
    private static final int COPY_THRESHOLD           =   10;
    private static final int REPLACEALL_THRESHOLD     =   11;
    private static final int INDEXOFSUBLIST_THRESHOLD =   35;

    public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }
    
    .
    .
    .

 

정적 멤버만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 게 아니다. 하지만 생성자를 명시하지 않으면 자동으로 컴파일러가 기본 생성자를 만든다. 사용자는 이 생성자가 자동 생성된 것인지 구분할 수 없다.

 

new 연산자로 인스턴스를 생성시키지 못하니 추상클래스로 만들 수 있지 추상 클래스로 만드는 것으로 인스턴스화를 막을 수 없다. 하위 클래스를 만들어 인스턴스화 하면 그만이다. 이를 본 사용자가 상속해서 쓰라는 뜻으로 오해할 수 있으니 더 큰 문제다.

 

public abstract class UtilityClass {

    public UtilityClass() {
        System.out.println("Constructor");
    }

    public static String Hello() {
        return "HELLO!";
    }
}

=======================================================================================

public class DefaultUtilityClass extends UtilityClass{

    public static void main(String[] args) {
        DefaultUtilityClass utilityClass = new DefaultUtilityClass();
    }
}

==> "Constructor"

 

 

다행히 생성자의 접근 지시자를 private으로 하면 간단하게 해결 할 수 있다.

public class UtilityClass {

    /**
     * Don't let anyone instantiate this class.
     */
    private UtilityClass() {
        throw new AssertionError();
    }

    public static String Hello() {
        return "HELLO!";
    }
}

 

사용자가 직관적으로 알 수 있게 주석으로 인스턴스화 할 수 없다고 달아두도록 하자. 또한 내부에서 실수로라도 생성자를 호출하지 않도록 AssertionError를 던져주자