- 변경 가능성을 최소화하라는 것은 불변성(불변 클래스)의 중요성과 연결된다.
- 불변 클래스란 그 인스턴스의 내부 값을 수정할 수 없는 클래스이다.
- 불변 인스턴스 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다.
- 자바 플랫폼 라이브러리 불변클래스 :
String, 기본 타입의 박싱된 클래스들,BigInteger,BigDecimal등
- 불변 클래스는 가변 클래스보다 설계, 구현, 사용이 쉬우며 오류 가능성이나 여지도 적고 훨씬 안전하다.
- 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다.
- 모든 필드를 final로 선언한다.
- 모든 필드를 private로 선언한다.
- 자신 외에는 내부 가변 컴포넌트에 접근할 수 없도록 한다.
- 변경이 필요하다면, 방어적 복사를 통해 수행할 수 있다.
불변 객체는 근본적으로 thread safe하므로 따로 동기화할 필요가 없다. 불변 객체는 다른 스레드에 영향을 줄 수 없으므로, 안심하고 공유할 수 있다. 따라서, 불변 클래스는 이미 만든 인스턴스를 최대한 재활용하는 것이 좋다.
public static final Complex ZERO = new Complex(0,0);
public static final Complex ONE = new Complex(1,0);
public static final Complex I = new Complex(0,1);-
이와 같이 자주 쓰이는 값을 상수로 제공하여 쉽게 재활용 할 수 있게 만들었다.
-
불변 클래스는 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않도록 정적 팩터리를 제공할 수 있으며, 박싱된 기본 타입 클래스 전부와
BigInteger등이 있다. -
정적 팩터리를 사용하면 여러 클라이언트가 인스턴스를 공유해 메모리 사용량과 가비지 컬렉션 비용이 줄어든다.
-
불변 객체는 자유롭게 공유할 수 있고, 불변 객체끼리는 내부 데이터를 공유할 수 있다.
-
객체 생성시 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
- ex.
Map의Key와Set원소로 사용하기 좋다.
- ex.
-
불변 객체는 그 자체로 실패 원자성을 제공한다. (추후 Item76)
- 실패 원자성 : 메서드에서 예외 발생 후에도 해당 객체는 메서드 호출 전과 동일한 유효한 상태인 것
- 값의 변경이 필요할 때, 반드시 독립적인 객체로 새로 만들어서 사용해야 한다.
- 생성, 관리해야하는 객체가 많아질 경우 비용 소모가 크다.
- 상속이 불가능하다. (
final클래스가 되므로) 재사용이 필요한경우 컴포지션으로 확장해야한다. (Item18) - 방어적 복사를 수행할 때, 데이터 내부 구조가 커지면 복사 비용이 증가한다. (퍼포먼스 저하)
Getter가 있다고 해서 무조건Setter를 구현하지 말자 (내부 가변 포인트 제공의 문제)- 클래스는 꼭 필요한 경우가 아니라면 불변이여야한다.
- 불변으로 만들 수 없는 클래스를 만들 때, 가변 포인트를 최대한 줄여야한다. (값 변경이 가능한 지점 최소화)
- 다른 적절한 이유가 없다면, 모든 필드는
private final이여야한다. - 생성자는 불변식 설정이 모두 완료된 객체를 생성해야한다.
- 특별한 이유가 없다면, 생성자와 정적 팩토리 메서드 외에는 그 어떤 초기화 메서드도
public으로 제공하면 안된다. (접근 제한자와 초기화 시 가변 포인트 문제) java.util.concurrent.CountDownLatch는 다음 원칙을 잘 지키고 있다.
- 스레드 간 동기화 도구 (Java Concurrent 패키지 내 존재)
- 지정된 횟수 만큼(동기화를 위해 메모리 가시성이 있는 volatile int state 사용) 카운트를 세면서 대기중인 스레드를 진행되도록 만드는 동기화 도구이다.
✅ 이 클래스는 다음 원칙을 잘 지킨다
- 내부 상태 (count)는 외부에서 직접 변경할 수 없음
- countDown()은 내부적으로만 상태 변경이 허용됨
- 불필요한 set 메서드 등은 존재하지 않음 → 불필요한 가변 포인트를 노출하지 않음
- 한 번 count가 0이 되면 그 이후 상태는 변경되지 않음 → 불변 상태 전이