아이템17. 변경 가능성을 최소화하라
2021-08-22 00:00:00 # Effective_Java
  • 불변 클래스: 그 인스턴스의 내부 값을 수정할 수 없는 클래스다.
  • 가변 클래스보다 설계하고 구현하고 사용하기 쉬우며, 오류가 생길 여지도 적고 훨씬 안전하다.

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

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

불변 클래스 예시

  • Complex.java
  • 사칙연산 메서드들이 인스턴스 자신을 수정하는 것이 아닌 새로운 Complex 인스턴스를 만들어내고 있음
  • 함수형 프로그래밍: 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴

불변 클래스의 특징

  • 불변 객체는 단순하다.
  • 불변 객체는 근본적으로 thread-safe 하여 따로 동기화할 필요가 없다.
  • 불변 객체는 안심하고 공유할 수 있다.
  • 아무리 복사해봐야 원본 객체이기에..!
  • clone 메서드나 복사 생성자를 제공하지 않는 게 좋다. (String의 복사 생성자는 필요 없다.)
  • 불변 객체끼리는 내부 데이터를 공유할 수 있다.
  • 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다!
  • 불변 객체 그 자체로 실패 원자성을 제공한다. (메서드에서 예외가 발생한 후에도 그 객체는 여전히 유효한 상태여야 한다.)

불변 클래스의 단점 및 대처 방법

  • 값이 다르면 반드시 독립된 객체로 만들어야 한다.
  • 흔히 쓰일 다단계 연산들을 예측하여 기본 기능으로 제공하는 방법
  • String과 String의 가변 동반 클래스 StringBuilder(StringBuffer도 있지..)

클래스가 불변임을 보장하기 위한 여러 방법들

  • final 클래스로 선언하는 가장 쉬운 방법도 있다.
  • 모든 생성자를 private 혹은 package-private으로 만들고 public 정적 팩터리를 제공하는 방법
    • 다수의 구현 클래스를 활용한 유연성을 제공하고, 다음 release에서 객체 캐싱 기능을 추가해 성능을 끌어올릴 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Complex 클래스를 생성자 대신 정적 팩터리를 사용해 불변 클래스로 만들어보기
public class Complex {
private final double re;
private final double im;
// private 생성자이기 때문에 다른 패키지에서 확장 불가
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이 아닌 필드에 캐시해두기
  • 이유: 똑같은 값을 다시 요청하면 캐시해둔 값을 반환하여 계산 비용을 절감할 수 있다.

정리

  • getter가 있다고 무조건 setter를 만들지 말라
  • 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
  • 모든 클래스를 불변으로 만들 수도 없다. 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자
  • 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.