2 minute read

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 메서드를 제공하지 않는 것이 좋다. (불필요하다)
  • 불변 객체끼리 자유롭게 내부 데이터 공유가 가능하다.
  • 상태가 변하지 않으므로 그 자체로 실패 원자성을 제공한다.
    • 잠깐이라도 불일치 상태에 빠질 가능성이 없다.

불변 객체의 단점

  • 값이 다른 객체는 반드시 독립된 두 불변 객체로 만들어야 한다.
    • 원하는 객체 생성까지 단계가 많고, 중간 단계의 객체들에 의한 성능 문제가 발생
    • 가변 동반 클래스 사용하기 : 복잡한 다단계 연산을 기본으로 제공해주는 클래스
      • StringBuilder

결론

  • 성능 저하로 불변 클래스 구현이 힘들다면 가변 동반 클래스를 사용하자.
  • Setter 사용을 자제하자 ; 꼭 필요한 경우가 아니라면 클래스는 불변이어야 한다.
  • 클래스를 불변으로 구현하기 힘들다면, 변경 가능한 부분을 최소화하자.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
    • 객체 상태를 초기화하는 메서드는 생성자, 정적 팩터리 메서드 외에는 없어야 한다.

Leave a comment