[이펙티브 자바] 아이템 17. 변경 가능성을 최소화하라

June·2022년 3월 3일
0

[이펙티브자바]

목록 보기
16/72

불변 클래스는 인스턴스 내부 값을 수정할 수 없는 클래스다. String, 기본 타입의 박싱된 클래스들 등이 불변 클래스의 예다.

불변 클래스 생성 규칙

아래는 불변 클래스를 만드는데 필요한 규칙이다.

  • 객체의 상태를 변경하는 메서드를 제공하지 않는다.
  • 클래스를 확장할 수 없도록 한다.
    • 하위 클래스에서 객체의 상태를 변하게 하는 것을 막는다. 상속을 막는 대표적인 방법은 클래스를 final로 선언하는 것이지만, 뒤에 다른 방법도 있다.
  • 모든 필드를 final로 선언한다.
    • 설계자의 의도를 명화깋 드러내고, 새로 생성된 인스턴스를 동기화 없이 다른 스레드로 건내기 위해서도 필요하다.
  • 모든 필드를 private으로 선언한다.
    • 필드가 참조하는 가변 객체를 클라이언트에서 접근해 수정하는 일을 막아준다. 기술적으로는 기본 타입 필드나 불변 객체를 참조하는 필드를 public final로만 선언해도 불변 객체가 되지만, 이렇게 하면 다음 릴리스에서 내부 표현을 바꾸지 못하므로 비추.
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
    • 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야 한다. 생성자, 접근자, readObject 메서드 모두에서 방어적 복사를 수행하라.
// 코드 17-1 불변 복소수 클래스 (106-107쪽)
public final class Complex {
    private final double re;
    private final double im;

    public static final Complex ZERO = new Complex(0, 0);
    public static final Complex ONE  = new Complex(1, 0);
    public static final Complex I    = new Complex(0, 1);

    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);
    }

    // 코드 17-2 정적 팩터리(private 생성자와 함께 사용해야 한다.) (110-111쪽)
    public static Complex valueOf(double re, double im) {
        return new Complex(re, 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 인스턴스를 만들어 반환하고 있다. 이처럼 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴을 함수형 프로그래밍이라 한다.

장점

불변 객체는 생성된 시점의 상태를 파괴될 때까지 그대로 간직한다. 불변 객체는 근본적으로 스레드 안전하여 따로 동기화할 필요 없다.. 여러 스레드가 동시에 상요해도 훼손되지 않는다. 그러므로 불변 객체는 안심하고 공유할 수 있다.

불변 객체는 아무리 복사해봐야 원본과 똑같고 그러므로 방어적 복사도 필요없다. 그래서 clone 메서드나 복사 생성자를 제공하지 않는게 좋다.

불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.

객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다. 구조가 복잡하더라도 불변식을 유지하기 수월하다.

불변 객체는 그 자체로 실패 원자성을 제공한다.(아이템 76). 즉 예외가 발생후에도 그 객체는 여전히 유효한 상태다.

단점

값이 다르면 반드시 독립된 객체로 만들어야 한다. 값의 가짓수가 많다면 이들을 모두 만드는데 큰 비용이 필요하다.

불변 클래스를 만드는 다른 방법

클래스가 불변이라면 자신을 상속하지 못하게 해야 하는데 두 가지 방법이 있다.

  1. final 클래스로 선언하는 것
  2. 모든 생성자를 private 또는 package-private으로 만들고 public 정적 팩터리 메서드 제공.

물론 모든 클래스를 불변으로 만들 수 없으나, 불변으로 만들 수 없는 클래스도 변경 가능한 부분은 최소화하자. 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.

생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 반환해야 한다.

0개의 댓글