Skip to content

item 17 dodo4513

황도영 edited this page Apr 3, 2020 · 1 revision

아이템17 변경 가능성을 최소화하라

  • 불변 클래스는 가변 클래스보다 설계, 구현, 사용하기 쉬우며 오류도 생길 여지도 적고 훨씬 안전하다. (ex String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal 등)

불변 클래스를 만드는 5가지 규칙

  • 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
  • 클래스를 확장할 수 없도록 한다.
  • 모든 필드를 final로 선언한다.
  • 모든 필드를 privated으로 선언한다.
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.

불변 복소수 클래스 예제

public final class Complex {
    private final double re;
    private final double im;
    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
    public double realPart() { return re; }
    public double imaginaryPart() { return im; }
    public Complex plus(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }
    public Complex minus(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }
    public Complex times(Complex c) {
        return new Complex(re * c.re - im * c.im,
                           re * c.im + im * c.re);
    }
    public Complex dividedBy(Complex c) {
        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp,
                           (im * c.re - re * c.im) / tmp);
    }

    @Override public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Complex))
            return false;
        Complex c = (Complex) o;
        // == 대신 compare를 사용하는 이유는 63쪽을 확인하라.
        return Double.compare(c.re, re) == 0
               && Double.compare(c.im, im) == 0;
    }
    @Override public int hashCode() {
        return 31 * Double.hashCode(re) + Double.hashCode(im);
    }
    @Override public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}
  • 사칙연산 메서드들이 인스턴스 자신은 수정하지 않고, 새로운 Complex 인스턴스를 만들어 반환하는 모습에 주목하자.
  • 위 처럼 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴을 함수형 프로그래밍이라 한다.
  • 또한 add와 같은 동사 대신 plus 전치사를 사용한 점도 주목하자. 이는 메서드가 객체의 값을 변경하지 않는다는 사실을 강조하려는 의도다. 참고로, 이 명명 규칙을 따르지 않은 BigInteger와 BigDecimal 클래스를 사람들이 잘못 사용해 오류가 발생하는 일이 자주 있다.

불변 객체의 장점과 단점

장점

  • 불변 객체는 근본적으로 스레드에 안전하여 따로 동기화할 필요 없다. 따라서 안심하고 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.
  • 불변 객체는 그 자체로 실패 원자성을 제공한다. 상태가 절대 변하지 않으니 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
  • 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩터리를 제공할 수 있다. 박싱된 기본 타입 클래스 전부와 BigInteger가 여기 속한다.

단점

  • 값이 다르면 반드시 독립된 객체로 만들어야 한다. 이 떄, 원하는 객체를 완성하기까지의 단계가 많고, 그 중간 단계에서 만들어진 객체들이 모두 버려진다면 성능 문제가 더 불거진다.

불변 클래스를 만드는 또 다른 설계 방법들

  1. final 클래스로 선언한다.
  2. 모든 생성자를 private or package-private로 선언하고 Public 정적 팩터리를 제공한다. 다수 구현 클래스를 활용한 유연성, 캐싱 기능 등 부가 작업 처리가 용이하기 때문에 이 방법이 최선일 때가 많다.

핵심정리

다른 합당한 이유가 없다면 모든 필드는 private final 이어야 한다!
생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
확실한 이유가 없다면 생성자와 정적 팩터리 외에는 그 어떤 초기화 메서드도 public으로 제공해서는 안 된다.
객체를 재활용할 목적으로 상태를 다시 초기화하는 메서드도 안 된다.

Clone this wiki locally