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

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

by 에드박 2021. 4. 20.

이따금 단순히 static 메서드와 static 필드만을 담은 클래스를 만들고 싶을 때가 있습니다.

(객체 지향적이지 못해서 남용하면 좋지않지만.. 나름의 쓰임새가 있다!)

 

java.lang.Math 와 java.util.Arrays처럼 기본 타입 값이나 배열 관련 메서드들을 모아놓을 수 있습니다.

 

Java Arrays document(출처 : https://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html)

 

java.util.Collections 처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드 혹은 정적 팩터리 메서드를 모아놓을 수 있습니다.

(자바 8부터 이런 메서드를 인터페이스에 모아둘 수 있습니다.)

Java Collections document(출처 : https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html)

 

final 클래스와 관련한 메서드들을 모아놓을 때도 사용합니다.

-> final 클래스를 상속해서 하위 클래스에 메서드를 넣는 건 불가능하기 때문입니다.

 

정리하면

static 메서드와 static 필드만 담을 클래스를 사용하는 경우

  • 기본 타입 값이나 배열 관련 메서드들을 모아놓고 싶은 경우
  • 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드 혹은 정적 팩터리를 모아놓는 경우
  • final 클래스와 관련한 메서드들을 모아놓고 싶은 경우

보통 이렇게 만든 클래스가 유틸리티 클래스입니다.

 

유틸리티 클래스는 인스턴스로 만들어서 사용하려고 설계한것이 아니기 때문에 생성자의 존재가 필요없습니다.

그렇다고 생성자를 명시하지 않는걸로 끝낸다면?

  • 컴파일러가 친절하게 기본 생성자를 추가해줍니다.

아래는 생성자를 명시하지 않은 클래스 입니다.

package item4_20210420;

public class PrivateConstructorEx1 {

    void doSomething() {
        System.out.println("뭔가를 실행");
    }
}

 

위의 코드를 컴파일한 뒤

-> 바이트 코드를 디컴파일한 파일을 보면 아래와 같이 기본 생성자가 추가되어 있습니다.

 

추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없습니다. 하위 클래스를 만들어서 인스턴스화 하면 그만입니다.

package item4_20210420;

public class PrivateConstructorEx2 {
    public static void main(String[] args) {
        MyAbstractClass myAbstractClass = new MyChildClass();
    }
}

abstract class MyAbstractClass {

    void doSomething() {
        System.out.println("뭔가를 할겁니다.");
    }
}

class MyChildClass extends MyAbstractClass {

    @Override
    void doSomething() {
        System.out.println("자식이 뭔가를 합니다.");
    }
}

 

더불어 추상클래스를 본 사용자는 상속해서 쓰라는 뜻으로 오해할 수도 있습니다.(아이템 19)

 

따라서 컴파일러가 기본 생성자를 만드는 경우는 오직 명시된 생성자가 없을 때뿐이니

간단하게 기본 생성자를 private 생성자로 추가하면 클래스의 인스턴스화를 막을 수 있습니다.

 

package item4_20210420;

public class UtilityClass {

    // 기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지용)
    private UtilityClass() {
        throw new AssertionError();
    }
    
    void doSomething() {
        System.out.println("뭔가를 실행함");
    }
}

 

기본 생성자의 접근자를 private 로 명시했으니 클래스 바깥에서는 기본 생성자에 접근할 수 없습니다.

즉, 클래스 외부에서는 인스턴스를 생성할 수 없습니다.

 

꼭 AssertionError()를 던질 필요는 없지만, 클래스 안에서 실수로라도 생성자를 호출하지 않도록 해줍니다.

 

그런데 "생성자가 분명 존재하는 데 호출할 수 없다니? " 라고 착각할 수 있고 직관적이지 않을 수 있습니다.

그래서 위의 코드처럼 적절한 주석을 달아두도록 합니다.( 주석도 싫지만.. )

 

이 방식은 상속을 불가능하게 하는 효과도 있습니다.

모든 생성자는 명시적이든 묵시적이든 상위 클래스의 생성자를 호출하게 되는데 ( 예시. super() ) 

상위 클래스의 생성자가 private 로 선언되어 있으니 하위 클래스가 접근할 길이 막혀있습니다.

댓글