클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다. (게터가 있다고 해서 무조건 세터를 만들지는 말자)
불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자 ⇒ 아이템 15와 종합하면, 다른 합당한 이유가 없다면 모든 필드는 private final이어야 함
클래스를 불변으로 만들기 위한 규칙 5가지
- 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다
- 클래스를 확장할 수 없도록 한다 → 하위 클래스에서 부주의하게 혹은 나쁜 의도로 객체의 상태를 변하게 하는 것을 막아줌
- final class로 만들기
- 생성자를 private 혹은 package-private으로 만들고 public 정적 팩터리를 제공하는 방법
- 모든 필드를 final로 선언하기 → thread safety 보장
- 모든 필드를 private으로 선언하기 → 클라이언트가 그 필드를 직접 접근해 수정하는 일을 막아줌
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 함
- 클라이언트에서 가변 객체의 참조를 얻으면 안됨
- 생성자, 접근자, readObject 메서드 모두에서 방어적 복사를 수행하기
⇒ 이 규칙은 좀 과한 감이 있어서 성능을 위해 조금 완화한다면, “어떤 메서드도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다” 로 가능함
불변 객체의 장점
- 단순하다 → 생성된 시점의 상태를 파괴될 때까지 그대로 간직함
- 근본적으로 스레드 안전하여 따로 동기화 할 필요 없다 → 안심하고 공유 가능 → 최대한 재활용하기
- 불변 클래스는 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩터리를 제공할 수 있음 (eg. BigInteger.valueOf)
- 메모리 사용량과 가비지 컬렉션 비용이 줄어듦
- 자유롭게 공유가 가능하여 방어적 복사도 필요 없음(아무리 복사해도 원본과 똑같으니 복사 자체가 의미가 없음)
- 불변 객체 끼리는 내부 데이터 공유도 가능함
- BigInteger.negate 메서드는 크기가 같고 부호만 반대인 새로운 BigInteger를 생성하는데 이 때 배열이 가변이지만 복사하지 않고 그대로 원본 인스턴스와 공유해도 됨
- 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많음
- 좋은 예로 불변 객체는 맵의 키와 집합의 원소로 쓰기에 안성맞춤
- 불변 객체는 그 자체로 실패 원자성(실패한다 하더라도 상태가 변경되지 않는 것)을 제공함(상태가 절대 변하지 않으니 잠깐이라도 불일치 상태에 빠질 가능성이 없음)
단점
- 값이 다르면 반드시 독립된 객체로 만들어야 한다는 점
- 예로, 백만 비트짜리 BigInteger에서 비트 하나를 바꿔야 한다고 했을 때, 새로 생성해야 함;; 비용이 매우 큼
- 예시로 String 클래스의 가변 동반 클래스는 StringBuilder임
⇒ 이러한 경우 가변 동반 클래스(companion class)를 package-private으로 두고 거기서 그러한 기능들을 구현하면 됨