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

다람·2025년 2월 20일
0

Effective Java

목록 보기
4/13
post-thumbnail

item4 에서는 정적 메서드와 정적 필드만을 담은 클래스를 왜 이런 클래스가 객체 지향적이지 않다는 것인지 어떻게 안전하게 만드는지를 말한다.

1. 정적 멤버만 담은 클래스의 쓰임새

1-1. 정적(static)인 필드와 메서드만 담은 클래스는 객체 지향적이지 않다

  • 객체 지향(OOP)은 보통 상태(필드)와 행동(메서드)을 갖는 객체들이 협력하여 프로그램을 설계하는 것이다.
  • 그러나 정적 멤버만 모아둔 클래스함수(메서드) 모음집에 가깝다.
    • 객체를 만들 필요가 없고, 내부 상태도 없는 경우가 많다. 객체를 만들 필요가 있을 때는 내부에 값을 저장하고 추적하기 위해서인데 정적 메서드와 정적 필드만을 담고 있다면 인스턴스마다 달라질 상태가 없다는 뜻이다.
    • 이를 두고 객체 지향적이지 않다라고 하지만 나름의 쓰임새가 존재한다.

정적 메서드와 필드만을 모아뒀지만 쓸모 있는 경우

  1. java.lang.Math
    • Math.abs(), Math.max(), Math.random() 같은 수학 연산 메서드static으로 제공한다.
  2. java.util.Arrays
    • 배열 관련 연산인 Arrays.sort(), Arrays.copyOf(), Arrays.toString() 등을 static 메서드로 제공한다.
  3. java.util.Collections
    • 컬렉션 관련 유틸 메서드가 모여 있다.
    • Collections.unmodifiableList(), Collections.singletonList() 등은 내부적으로 인터페이스를 구현한 객체를 생성해서 반환하기도 한다.
  4. final 클래스와 관련된 메서드들을 모아둘 때 사용한다.
    - final 클래스를 상속해서 "추가 기능"을 넣고 싶어도, final 클래스는 상속이 불가하기 때문이다.

    순수한 기능만 모아서 제공해야 할 때는 정적 유틸리티 클래스를 사용한다.

2. 정적 멤버만 모은 유틸 클래스는 왜 인스턴스를 막아야 할까?

  1. 인스턴스 상태(필드)가 전혀 없다
    • 객체로 만들어봐야 내부에 저장할 것이 없기 때문에 의미가 없다.
  2. 모든 기능이 static 메서드로 제공
    • 정적 메서드는 인스턴스 생성없이 호출 가능하다.
  3. 사용자가 구분할 수 없다
    • 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자(public)을 만들어주기 때문에 사용자가 자동으로 생성된 생성자인지 알 수 없다.
  4. 추상 클래스로 만들면 상속할 수 있다
    • 사용자는 추상 클래스를 보면 상속해서 사용하라는 뜻으로 오해할 수 있다.

유틸리티 클래스는 인스턴스를 만들어 쓰려고 설계한 것이 아니다. 그냥 두게되면 누군가가 실수로 인스턴스를 만들거나 오해해서 상속해서 쓰려고 할 수 있다. 그렇기 때문에 인스턴스 화를 막아줘야한다.

3. 인스턴스화를 막는 방법(private)

자바는 클래스에 생성자를 전혀 작성하지 않으면, 기본 생성자(public)를 자동으로 만들어 준다.

public class UtilityClass {
    // 여기에 아무 생성자도 없으면...
    // 컴파일러가 자동으로 public UtilityClass() {} 를 만들어낸다.
}

이렇게 되면 생성자를 통한 인스턴스화가 가능해져 버린다.

private 생성자를 추가한다.

public class UtilityClass {
    // 기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지)
    private UtilityClass() {
        throw new AssertionError("No UtilityClass instances for you!");
    }

    public static String doSomething(...) {
        // ...
    }
    // 그 외 정적 메서드 및 필드...
}
  • 생성자를 명시적으로 private으로 만들어서 클래스 바깥에서 인스턴스화할 수 없도록 해줘야한다.
  • 내부에서도 실수로 호출되지 않도록 throw new AssertionError() 같은 예외를 던질 수도 있다.
  • 굳이 예외를 던질 필요는 없지만 실수로라도 생성자를 호출하지 않도록 명확히 하기 위함이다.
  • private을 사용하면 상속도 불가능하게 된다. 하위 클래스의 생성자는 반드시 상위 클래스 생성자를 호출해야 하는데, 상위 클래스의 기본 생성자가 private이므로 상속이 불가능하게 된다.
  • 결과적으로 이 클래스는 하위 클래스를 만들 수도 없다.
  • 위의 방식으로 생성자를 추가하게 되는 경우 직관적이진 않기 때문에 적절한 주석을 달아두는 것이 좋다.

4. 결론

  1. 인스턴스화할 필요가 없는 유틸리티 클래스는 그냥 두면 컴파일러가 기본 생성자를 자동 생성하므로, private 생성자를 명시해야 한다.
  2. 인스턴스화 방지는 물론 상속도 막을 수 있어, 이 클래스는 오로지 유틸리티성 함수만을 제공한다는 의도를 드러낼 수 있다.
  3. throw new AssertionError() 예외를 던져서 실수로라도 생성자를 호출하는 일을 방지하고, 주석을 통해서 인스턴스화 불가 의도를 전달한다.
profile
개발하는 다람쥐

0개의 댓글