Item17. 변경 가능성을 최소화하라!
불변 클래스. Immutable class
인스턴스 내부 값을 수정할 수 없는 클래스 불변 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 변경되지 않는다. 불변 클래스는 가변 클래스보다 설계, 구현, 사용이 쉽고, 오류가 생길 여지가 적어 안전하다.
불변 클래스 생성 규칙
객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
클래스를 확장할 수 없도록 한다.
- 하위 클래스에서 객체의 상태를 변경하는 것을 막아준다.
- 상속을 막는 대표적 방법에는
final
로 클래스를 선언하는 것이 있다.
모든 필드를
final
로 선언한다.- 시스템이 강제하는 수단을 이용해 설계자의 의도를 명확히 드러내는 방법
모든 필드를
private
으로 선언한다.- 필드가 참조하는 가변 객체를 클라이언트에서 직접 접근해 수정하는 일을 막아준다.
자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
- 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야 한다.
1 2 3 4 5 6 7 8 9 10
public class Cafe { private final String name; private final Address address; public Cafe(String name, Address address) { this.name = name; this.address = address; // 직접 참조 } // getter }
1 2 3 4
public class Address { private String region; // getter & setter }
- 이 경우
Cafe
에서Address
가변 객체를 직접 참조하고 있다. - 이는
Address
객체가 변경된다면Cafe
객체도 변경되는 것을 의미한다. - 그렇기에 불변성이 깨진다.
1 2 3 4
public Cafe(String name, Address address) { this.name = name; this.address = new Address(address.getRegion()); }
- 불변으로 만들기 위해
Address
객체를 새로 생성해 사용한다.
불변 클래스의 장단점
장점
- 불변 객체는 단순하다.
- 불변 객체는 생성 시점부터 파괴 시점까지 상태를 그대로 간직한다.
- 모든 생성자가 불변식을 보장한다면 별다른 노력없이 영원히 불변으로 남는다.
- 오류가 생길 여지가 적어 안전한 사용이 가능해진다.
- 불변 객체는 근본적으로
thread-safe
를 보장해 안전하며 따로 동기화가 필요 없다.- 어떤 스레드가 사용해도 절대 훼손되지 않는다.
- 그렇기에 불변 객체는 안심하고 공유하는 것이 가능하다.
- 또 한번 만든 불변 인스턴스를 최대한, 안전하게 재활용하는 것이 가능하다.
- 불변 객체는 자유롭게 공유할 수 있고, 불변 객체끼리 내부 데이터 공유도 가능하다.
- 방어적 복사가 필요 없다.
- 아무리 복사해봐야 원본과 같으니 복사의 의미가 없다.
- 그러니 불변 클래스는
clone
메서드나 복사 생성자를 제공하지 않는 것이 좋다.
- 불변 객체의 필드가 가변 객체의 필드를 참조하더라도
final
,private
제한자로 접근, 변경을 제한하기 때문에 여전히 불변을 유지한다.
- 방어적 복사가 필요 없다.
- 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
- 값이 불변인 구성요소들로 이뤄진 객체라면 그 구조가 어떻든 불변식을 유지하기 수월하기 때문
- 불변 객체는 그 자체로 실패 원자성을 제공한다.
- 상태가 절대 변하지 않으니 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
단점
- 값이 다르면 반드시 독립된 객체가 필요하다는 것이다.
- 즉, 새로운 인스턴스 생성 비용에 대한 문제도 고려해야 할 것이다.
- 다단계 연산들을 예측하여 기본 기능으로 제공해 이와 같은 문제를 해결할 수 있다.
1
2
3
4
5
6
7
public static void main(String[] args) {
String count = "";
for (int i = 0; i <10; i++) {
count +=String.valueOf(i);
System.out.println(count);
}
}
- 위 코드는
String
인스턴스를 계속해서 생성하고 출력한다.String
의 가변 동반 클래스인StringBuilder
를 사용해 이러한 문제를 해결할 수 있다.
불변 클래스 설계 방법
앞서 살펴본 것처럼
final 클래스
로 만들어 자신을 상속하지 못하게 막는 방법- 이 규칙이 약간 과한 감이 있어, 성능을 위해 약간 완화한 경우도 있다.
- 계산 비용이 큰 값을 나중에 계산하여
final
이 아닌 필드에 캐시해놓고 사용하는 방법 - 똑같은 값 요청 시 캐시해둔 값을 반환해 계산 비용을 줄인다.
- 순전히 그 객체가 불변이기 때문에 몇 번을 계산해도 항상 같은 결과를 반환함이 보장되어 이러한 방법을 사용할 수 있다.
- 계산 비용이 큰 값을 나중에 계산하여
- 이 규칙이 약간 과한 감이 있어, 성능을 위해 약간 완화한 경우도 있다.
모든 생성자를
private
혹은package-private
으로 만들고,public 정적 팩터리
를 제공하는 방법1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 생성자 대신 정적 팩터리를 사용한 불변 클래스 public class Complex { private final double re; private final double im; 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); } //.. 생략 }
정리
Getter
가 있다고 해서 무조건Setter
를 만들지는 말자.- 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
- 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소화하자.
- 객체가 가질 수 있는 상태의 수를 줄이면 예측이 쉬워지고, 오류 가능성 낮아진다.
- 꼭 변경 필요한 필드 빼고는
final
로 선언하자. - 아이템 15 내용을 통해, 다른 이유 없다면 모든 필드는
private final
로 선언한다.
- 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
- 확실한 이유 없이 생성자와 정적 팩터리 외의 어떤 초기화 메서드도
public
으로 제공되어서는 안된다.
- 확실한 이유 없이 생성자와 정적 팩터리 외의 어떤 초기화 메서드도
Comments powered by Disqus.