Effective Java : 아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라

    들어가기 전

    이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다. 


    아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라. 

    정적 메서드만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 클래스가 아니다. 따라서 인스턴스 생성을 방지하는 것이 좋다 

    • 추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다. 
    • private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.
    • 생성자에 주석으로 인스턴스화가 불가능한 이유를 설명하는 것이 좋다.
    • 상속을 방지할 때도 같은 방법을 사용할 수 있다. 

    어플리케이션을 설계할 때, 어떤 경우에는 특정 클래스의 인스턴스를 만들 필요가 없는 경우가 있다. 오히려 인스턴스를 만들지 않는 것을 권장하는 경우도 존재한다. 대표적인 경우는 유틸리티성 클래스다. 유틸리티성 클래스는 static 메서드만 가지고 있는 클래스를 의미한다 대표적인 유틸리티 클래스는 StringUtils 같은 녀석들이 존재한다. 예를 들면 다음과 같이 클래스를 작성해 볼 수 있다.

    public abstract class UtilityClass {
    
        public static String hello() {
            return "hello";
        }
        ...
    }

    이런 유틸리티 클래스에서 인스턴스가 생성되는 것을 막아야한다. 여기서는 인스턴스 생성이 필요없는 클래스의 인스턴스 생성을 막는 방법을 알아보고자 한다. 


    방법1. 추상 클래스를 사용한다.  → 완벽하지 않음. 

    추상 클래스로 유틸리티 클래스를 작성한다. 이렇게 작성하게 된다면, Utility 클래스를 직접 생성하는 것은 막을 수 있다. 하지만 여전히 UtilityClass의 인스턴스를 생성할 수 있는 방법은 남아있다. 또한 abstract 라는 키워드를 보면, '이 클래스는 상속을 한 후에 사용하세요'라는 용도의 클래스로 오해를 할 가능성도 존재한다. 

    public abstract class UtilityClass {
    
        public static String hello() {
            return "hello";
        }
    
        public static void main(String[] args) {
            System.out.println(UtilityClass.hello());
        }
    }

    자바에서는 아무런 생성자도 설정하지 않는 경우, 자동적으로 기본 생성자가 설정된다. 따라서 UtilityClass (추상 클래스)도 기본 생성자가 생성된다. 

    public class DefaultUtilityClass extends UtilityClass{
    
        public DefaultUtilityClass() {
            System.out.println("DefaultUtilityClass constructor called");
        }
    
        public static void main(String[] args) {
            DefaultUtilityClass defaultUtilityClass = new DefaultUtilityClass();
            System.out.println(defaultUtilityClass.hello());
        }
    }

    이 때, UtilityClass를 상속한 자식 클래스인 DefaultUtilityClass를 작성하고 DefaultUtilityClass의 인스턴스를 생성하는 작업을 진행해본다. 위 코드의 실행 결과를 보면 추상 클래스라도 인스턴스가 생성되는 것을 볼 수 있다. 

    UtilityClass Constructor calls
    hello

    자식 클래스의 인스턴스를 생성할 때, 부모 클래스의 생성자를 호출한다. 이 때 추상 클래스의 인스턴스가 생성된다. 즉, 추상 클래스만으로는 인스턴스 생성을 막을 수 없다는 것이다.


    방법2. private 생성자 + 주석 설명

    인스턴스화를 막기 위해서는 이 방법을 사용하면 된다. private 생성자를 이용하면 인스턴스화를 막을 수 있다. 

    • private 생성자를 사용하면, 상속도 막을 수 있다.

    이런 특성이 적용되기 때문에 앞서 이야기 했던 추상 클래스에서의 인스턴스 생성을 막을 수 있게 되는 것이다. 그런데 한 가지 문제점이 있다. 바로 클래스 내부에서는 생성자 호출이 가능하고, 인스턴스를 생성할 수 있다는 것이다. 따라서 이 부분도 방지가 필요할 수 있는데, 이것은 private 생성자 내부에서 에러를 던지도록 하면 된다. 

    public class UtilityPrivateClass {
    
        private UtilityPrivateClass() {
            /**
             * Creation of UtilityPrivateClass instance is forbidden.  
             */
            
            System.out.println("UtilityPrivateClass Constructor calls");
            throw new AssertionError();
        }
    
        public static String hello() {
            return "hello";
        }
    
        public static void main(String[] args) {
            System.out.println(UtilityPrivateClass.hello());
        }
    }

    클래스 내부에서 생성자를 호출하는 경우가 있을 수 있기 때문에 private 생성자 내부에 AssertionError를 던진다. 

    AssertionError는 Exception의 상위 클래스인 Throwable이다. Throwable은 Try ~ Catch 용도로 생성된 것이 아니다. 이것은 명백한 오류이며, 잘못된 것이기 때문에 에러를 던지는 것으로 명시하는 것이다. 

     


    Item4 정리

    • 인스턴스화를 막기 위해서는 Private 생성자 + 주석으로 왜 생성 못하는지를 알려주는 것이 좋은 방법이다.
    • 추상 클래스로는 인스턴스화를 막을 수 없다. 

    댓글

    Designed by JB FACTORY