본문 바로가기
이펙티브 자바

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

by 이상한나라의개발자 2024. 1. 3.

해당 내용의 핵심은 인스턴스를 생성할 필요가 없는 클래스를 설계할 때, 개발자가 실수로 인스턴스를 생성하는 것을 방지합니다.

예를 들어, 유틸리티성 클래스를 만들 때 이 클래스는 정적 필드만 포함하고 인스턴스화할 필요가 없습니다. "Java Math " 클래스가 이러한 유형입니다. 자바에서는 생성자를 명시적으로 정의하지 않으면 컴파일러가 자동으로 기본 생성자를 제공합니다. 이로 인해 개발자가 실수로 이러한 유틸 클래스의 인스턴스를 생성할 수 있습니다.

 

이를 방지 하기 위해 private 생성자를 사용합니다. private 생성자는 외부에서의 접근을 차단하여 클래스의 인스턴스화를 방지 합니다. 또한 이 생성자 내부에는 인스턴스화를 시도할 경우 예외를 던지는 코드를 작성할 수도 있습니다.

 

public final class Math {

    private Math() {}

    public static final double PI = 3.14159265358979323846;

    private static final double DEGREES_TO_RADIANS = 0.017453292519943295;

    private static final double RADIANS_TO_DEGREES = 57.29577951308232;

    @HotSpotIntrinsicCandidate
    public static double sin(double a) {
        return StrictMath.sin(a); // default impl. delegates to StrictMath
    }

    @HotSpotIntrinsicCandidate
    public static double cos(double a) {
        return StrictMath.cos(a); // default impl. delegates to StrictMath
    }
}

 

public class UtilityClass {
    // 기본 생성자가 자동 생성되지 않도록 private 생성자를 선언
    private UtilityClass() {
        throw new AssertionError("Utility class should not be instantiated");
    }

    public static void utilityMethod() {
        // 유틸리티 메서드 구현
    }
}

 

private 생성자를 사용함으로써 외부에서 "newUtlityClass" 를 호출하는 것이 불가능 합니다. 또한, 생성자 내부에서 "AssertionError" 를 던지는 것은 클래스 내부에서 실수로 생성자를 호출하는 것을 방지하기 위한 추가적인 조치입니다.

 

이런 방식으로 클래스 설계 시 private 생성자를 사용하면, 명시적으로 인스턴스화를 방지할 수 있으며, 이는 클래스의 설계 의도를 더 명확하게 전달하는데 도움이 됩니다.

 

스프링 프레임워크 기준에서 보면 유틸성 클래스( 인스턴스를 만들 필요가 없는 ) 의 경우 대부분 abstract 로 만들어 인스턴스화를 막았습니다. 하지만 여기에는 문제점이 존재하게 되는데요. abstract를 선언한다고 해도 익명 클래스를 구현하여 인스턴스를 만들 수 있으며, 상속을 받아 상위 클래스를 생성하는 문제점이 발생하게 됩니다. 

public abstract class UtilClassExample {
	
    // 기본 생성자는 별도로 만들지 않아도 default 로 생성이 됩니다.
    public UtilClassExample() {
        System.out.println("true = " + true);
    }

    public static LocalDateTime now() {
        return LocalDateTime.now();
    }
}

 

public class TimeClassExam extends UtilClassExample {

    public static void main(String[] args) {
        TimeClassExam utilClassExample = new TimeClassExam();
    }
}

 

위와 같이 상속을 받게되면 자동적으로 인스턴스를 생성할 때, super 이 호출 되므로 생성자가 호출 되게 됩니다. 그러므로 private 로 인스턴스 생성을 명시적으로 제한해야 합니다.

 

 

참고 - 유틸클래스 기준 

  • 유틸 클래스는 인스턴스 변수가 없어야 합니다. 즉 모든 메서드는 정적(static)이여야 합니다.
  • 클래스 내의 메서드들은 범용적이고 재사용이 가능하야 합니다. 특정 상황에 국한되지 않는 일반적인 기능이여야 합니다 ( 비즈니스 로직 x )
  • 유틸클래스는 인스턴스화 되면 안됩니다.