item 17. 변경 가능성을 최소화하라
불변 클래스
- 인스턴스의 내부 값을 수정할 수 없는 클래스
- 불변 클래스의 인스턴스는 객체가 생성되는 시점에 초기화되고 소멸이 될 때까지 값이 변경되지 않는다.
- 가변 클래스보다 설계, 구현 및 사용이 쉽다.
- 값이 변경되지 않으니 오류가 발생할 일도 적고 안전하다.
불변 클래스 설계 조건
- 객체 상태 변경 메서드 (= 변경자)를 제공하지 않는다.
- 다시 말해, setter 혹은 필드 정보를 변경하는 메서드를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다.
- 클래스를 final로 선언한다.
- 정적 팩터리 메서드를 사용한다.
- 다른 패키지에서는 이 클래스 확장이 불가능하다.
- 다수 구현 클래스를 활용하는 유연성을 제공한다.
- 차기 릴리즈에서 객체 캐싱을 추가해 성능을 높일 수 있다.
- 모든 필드를 final로 선언한다.
- 필드에 대해 수정을 막는다. (생성자에서 딱 한번만 초기화된다.)
- 인스턴스 동기화 없이도
thread-safe
하게 처리된다.
- 모든 필드를 private로 선언한다.
- 필드가 참조하는 가변 객체를 클라이언트에서 직접 접근하여 수정하는 일을 막는다.
public final
: 다음 릴리즈에서 내부 표현을 변경할 수 없으므로 권장X
- 자신 외에는 내부 가변 컴포넌트에 접근할 수 없도록 한다.
- 인스턴스 내 가변 객체의 참조를 얻을 수 없게 해야한다.
- 생성자, 접근자,
readObject
메서드에서 모두 방어적 복사를 수행한다.
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 필드에 대한 읽기 전용 (getter) 메서드만 제공
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
함수형 프로그래밍
- 자료 처리를 수학적 함수의 계산으로 취급하고, 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임
- 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 값이 변하지 않는다.
- 코드에서 불변 영역의 비율이 높아지는 장점을 누릴 수 있다.
불변 객체의 장점
- 불변 객체는 생성부터 소멸될 때 까지 변하지 않는다.
- 근본적으로
thread-safe
하므로 동기화가 불필요하다.
- 안심하고 공유가 가능하다.
- 불변 클래스라면 최대한 재사용하자.(캐싱)
- 불변 객체는 바뀌지 않기에 복사해도 동일하므로 방어적 복사본, clone 메서드를 제공하지 않는 것이 좋다. (불필요하다)
- 불변 객체끼리 자유롭게 내부 데이터 공유가 가능하다.
- 상태가 변하지 않으므로 그 자체로 실패 원자성을 제공한다.
- 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
불변 객체의 단점
- 값이 다른 객체는 반드시 독립된 두 불변 객체로 만들어야 한다.
- 원하는 객체 생성까지 단계가 많고, 중간 단계의 객체들에 의한 성능 문제가 발생
- 가변 동반 클래스 사용하기 : 복잡한 다단계 연산을 기본으로 제공해주는 클래스
결론
- 성능 저하로 불변 클래스 구현이 힘들다면 가변 동반 클래스를 사용하자.
- Setter 사용을 자제하자 ; 꼭 필요한 경우가 아니라면 클래스는 불변이어야 한다.
- 클래스를 불변으로 구현하기 힘들다면, 변경 가능한 부분을 최소화하자.
- 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
- 객체 상태를 초기화하는 메서드는 생성자, 정적 팩터리 메서드 외에는 없어야 한다.
Leave a comment