Effective Java - Item 17

Updated:

Effective Java

Item 17. 변경 가능성을 최소화하라

불변 클래스

인스턴스의 내부 값을 수정할 수 없는 클래스 EX) BigInteger, BigDecimal 가변 클래스보다 설계, 구현, 사용하기 쉬우며 / 오류가 생길 여지가 적으므로 훨씬 안전하다.

불변 클래스로 만들기위한 5가지 규칙

  1. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
  2. 클래스를 확장(상속)할 수 없도록 한다.
    클래스를 final로 선언하거나, 생성자를 private으로 선언하여 정적 팩터리 메소드를 제공한다.
    final 클래스는 생성자에서 딱 1회 초기화 할 수 있기 때문이다.
  3. 모든 필드를 final로 선언한다.
  4. 모든 필드를 private으로 선언한다.
    3번 예시와 같이 모든 필드를 private final로 선언하면 된다.
    public final로 처리해도 불변식은 보장하지만, public final은 외부 클래스에서 해당 객체를 참조할 가능성이 있으므로, 다음 릴리즈에서 내부 표현을 변경하지 못하므로 지양한다.
  5. 자신 외에는 내부 가변 컴포넌트에 접근할 수 없도록 한다.
    클라이언트에서 가변 객체에 접근을 막아야 한다.
    접근자 메서드가 그 필드를 반환하여도 안되며, 생성자 / 접근자 / readObject 메서드 모두 방어적 복사를 수행한다.
final public class InvariantClass{
  // 3,4번 : 모든 필드는 final private
  final private int num;
  final private char[] charArr;
  
  // 2번 : 생성자를 private, 정적 팩터리 메서드 제공
  public final InvariantClass instance = new InvariantClass();
  private InvariantClass() {
    num = 10;
    charArr = new char[10];
  }
  public InvariantClass getInstance() {
    return instance;
  }
  // 5번 : 가번 객체의 접근을 막는다
  public int getNum() {
    return num;
  }
  // 5번 : 방어적 복사를 수행한다
  public char[] getCharArr() {
    return charArr.clone();
  }
  // 1번 : 변경자를 제공하지 않는다
  private void setNum(int num) {
    this.num = num;
  }
}

함수형 프로그래밍

피연산자에 함수를 적용하여 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴.
코드가 불변인 영역의 비율이 높아지므로 안전하다.

불변 객체의 장점

  • 단순하다.
    생성된 시점부터 파괴될 때까지 처음 그대로 상태를 간직한다.

  • 스레드 안전하여 동기화 할 필요가 없다.
    인스턴스는 힙에 할당되어 공유자원이다.
    즉, 모든 스레드가 이 공유자원에 접근하여 사용할 수 있으니 동기화를 해야하지만, 불변 객체는 여러 스레드가 동시에 사용해도 훼손되지 않는다.

따라서 스레드 안전하며, 동기화를 해주지 않아도 된다.

  • 한번 만든 인스턴스를 재활용 할 수 있다.

자주 사용되는 정적 팩터리 메서드로 인스턴스를 생성하여 재활용한다.
EX) BigInteger, Wrapper Class 메모리 사용량 & GC 감소

  • clone 메서드나, 복사 생성자를 제공하지 않는다.
    아무리 복사해봐야 원본과 똑같으므로 의미가 없다.

  • 불변 객체는 자유롭게 공유하며, 불변 객체끼리는 내부 데이터를 공유할 수 있다.


BigInteger는 내부에 mag이라는 가변 배열을 가진다.
negate 메서드는 가변 배열의 참조값을 넘기고, 부호만 반대로 하여 반환한다.
즉 배열을 복사하지 않고 원본 인스턴스와 공유 하는 것이다.

  • 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
    구조가 복잡하더라도, 불변식을 유지하기 훨씬 수월하다.
    Map의 Key와 집합(Set)의 원소로 불변 객체를 사용하기 좋다.
    맵이나 집합은 안에 담긴 값이 바뀌면 불변식이 허물어지는데, 불변 객체는 그럴 걱정이 없다.

  • 실패 원자성을 제공한다.
    [실패 원자성] : 메서드에서 예외가 발생한 수에도 그 객체는 여전히 호출전과 똑같은 유효한 상태여야 한다.
    불변 객체의 메서드는 내부의 상태를 바꾸지 않으니 이 성질을 만족한다.

출처

Joshua Bloch. Effective Java 3/E. n.p.: 인사이트, 2018년 11월 1일.

Reference

https://jyami.tistory.com/79