불변 클래스란 간단히 말해 그 인스턴스의 내부 값을 수정할 수 없는 클래스다.
불변 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다.
클래스를 불변으로 만들기 위해서는 아래 규칙을 따르면 된다.
객체의 상태를 변경하는 메서드를 제공하지 않는다.
클래스를 확장할 수 없도록 한다.
모든 필드를 final로 선언한다.
모든 필드를 private로 선언한다.
자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
위와 같은 규칙을 만족하는 불변 클래스로는 만든 불변 객체는 근본적으로 스레드에 안전하여 따로 동기화할 필요가 없다.
불변 객체에 대해서는 그 어떤 스레드도 다른 스레드에 영향을 줄 수 없으니 불변 객체는 안심하고 공유할 수 있다.
그리고 불변 클래스는 한번 만든 인스턴스를 최대한 활용하기를 권한다.
가장 쉬운 재활용 방법은 자주 쓰이는 값들을 상수로 제공하는 것이다.
더 나아가 불변 클래스는 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복으로 생성하지 않게 해주는 정적 팩터리를 제공할 수 있다.
불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.
// Ex) BigInteger의 negate 메서드
public class BigInteger extends ... {
final int signum;
final int[] mag;
// ... 생략
public BigInteger negate() {
return new BigInteger(this.mag, -this.signum);
}
객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
값이 바뀌지 않는 구성 요소들로 이뤄진 객체라면 그 구조가 아무리 복잡하더라도 불변식을 유지하기 훨씬 수월하기 때문이다.
좋은 예로, 불변 객체는 맵의 키와 집합의 원소로 사용하기 안성맞춤이다.
또 불변 객체는 그 자체로 실패 원자성을 제공한다.
상태가 절대 변하지 않으니, 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
불변 클래스의 단점으로는 값이 다르면 반드시 독립된 객체로 만들어야 한다는 것이 있다.
변경 가능성 최소화에 대해 정리하면 우선 게터가 있다고 해서 무조건 세터를 만들지는 말자.
그리고 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
특히 단순한 값 객체는 항상 불변으로 만드는 것이 좋다.
불변 클래스의 단점으로는 특정 상황에서 잠재적 성능 저하인데 이는 불변 클래스와 쌍을 이루는 가변 동반 클래스를 public 클래스로 제공하는 방법으로 해결할 수 있다.
(관련 참고 이슈 : https://github.com/Java-Bom/ReadingRecord/issues/47)
그리고 모든 클래스를 불변으로 만들 수는 없을 것이다.
불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이는 것이 좋다.
그리고 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.