[Effective Java 3/e] 아이템 17 - 변경 가능성을 최소화하라


아이템 17 - 변경 가능성을 최소화하라

자바의 대표적인 불변 클래스

  • String, BigInteger, BigDecimal

클래스를 불변으로 만들기 위한 다섯 가지 규칙

  • 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
  • 클래스를 확장할 수 없도록 한다.
    • 상속을 막는 대표적인 방법 - final로 선언
  • 모든 필드를 final로 선언한다.
  • 모든 필드를 private로 선언한다.
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.

불변 복소수 클래스

public class Complex {
  private final double re;
  private final double im;

  public Complex(double re, double im) {
    this.re = re;
    this.im = im;
  }

  public double realPart() { return re; }
  public double imaginaryPart() { return im; }

  public Complex plus(Complex c) {
    return new Complex(re + c.re, im + c.im);
  }
}
  • plus 메서드는 자신의 인스턴스를 수정하지 않고 새로운 Complex 인스턴스를 만들어 반환한다.

불변 객체의 장점

  • 불변 객체는 단순하다.
  • 불변 객체는 근본적으로 스레드 안전하여 따로 동기화할 필요 없다.
  • 불변 객체는 안심하고 공유할 수 있다.
    • 불변 클래스라면 한번 만든 인스턴스를 최대한 재활용하기를 권장
    • 가장 쉬운 재활용 방법은 상수로 제공
      • public static final Complex ZERO = new Complex(0, 0);
  • 불변 객체끼리는 내부 데이터를 공유할 수 있다.
  • 불변 객체들을 구성요소로 사용하면 이점이 많다.
    • 불변 객체는 맵의 키와 집합(Set)의 원소로 쓰기 좋다.
  • 불변 객체는 그 자체로 실패 원자성을 제공한다.
    • 실패 원자성(failure atomicity) - 메서드에서 예외가 발생한 후에도 그 객체는 여전히(메서드 호출 전과 똑같은) 유효한 성질이어야 한다.

불변 객체의 단점

  • 값이 다르면 반드시 독립된 객체로 만들어야 한다.
    • 값의 가지수가 많다면 큰 비용이 든다.
  • 해당 문제를 대체 하는 방법
    • 흔히 쓰일 다단계 연산들을 예측하여 기본 기능으로 제공하는 방법
      • 다단계 연산을 기본으로 제공하면 더 이상 각 단계마다 객체를 생성하지 않아도 된다.

생성자 대신 정적 팩터리를 사용한 불변 클래스

public class Complex {
  private final double re;
  private final double im;

  private Complex(double re, double im) {
    this.re = re;
    this.im = im;
  }

  public static Complex valueOf(double re, double im) {
    return new Complex(re, im);
  }
}
  • 패키지 바깥의 클라이언트에서 바라본 이 불변 객체는 사실상 final
  • 정적 팩터리 방식은 다수의 구현 클래스를 활요한 유연성 제공, 객체 캐싱 기능을 추가해 성능을 끌어올릴 수도 있다.

정리

  • 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
  • 불변으로 만들 수 없는 클래스라도 변경할수 있는 부분을 최소한으로 줄이자.
  • 다른 합당한 이유가 없다면 모든 필드는 privte final 이어야 한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
  • 위의 규칙을 잘 지킨 클래스 - java.util.concurrent.CountDownLatch 클래스