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

wisdom·2022년 8월 18일
0

Effetctive Java

목록 보기
17/80
post-thumbnail

✔️ 자바 플랫폼 라이브러리의 불변 클래스

  1. String
  2. 기본 타입의 박싱된 클래스들
    ex) Integer, Long, Double
  3. BigInteger
  4. BigDecimal

✔️ 불변 클래스 만들기 위한 규칙 (5가지)

  • 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
  • 클래스를 확장할 수 없도록 한다.
    → 2가지 방법이 있다. (클래스 상속 방지하는 방법 참고)
  • 모든 필드를 final로 선언한다.
  • 모든 필드를 private으로 선언한다.
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
    생성자, 접근자, readObject 메서드 모두에서 방어적 복사를 수행하라.

✔️ 불변 클래스 예시: 복소수 클래스

// 불변 복소수 클래스
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)";
    }
}

📌 final 클래스

  • 확장이 불가능하도록 Complex 클래스를 final로 선언하였다.

📌 re, im 필드

  • 모든 필드는 final, private으로 선언한다.

📌 사칙연산 메서드 (plus, minus, times, dividedBy)

  • 인스턴스 자신은 수정하지 않고 새로운 Complex 인스턴스를 만들어 반환한다.
  • 객체의 값을 변경하지 않는다는 사실을 강조하기 위해, 메서드 이름으로 동사(add) 대신 전치사(plus)로 사용했다.
  • 함수형 프로그래밍: 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴.

✔️ 불변 객체 장점

  • 근본적으로 스레드 안전(thread-safe)하여 동기화할 필요가 없다.
    다른 스레드에 영향을 끼치지 않으므로 안심하고 공유할 수 있다.
    방어적 복사도 필요 없다. 따라서, clone 메서드나 복사 생성자를 제공하지 않는 게 좋다.
  • 불변 객체끼리는 내부 데이터를 공유할 수 있다.
  • 객체를 만들 때 불변 객체들을 구성요소로 사용하면, 불변식을 유지하기 훨씬 수월하다.
    ex) Map의 key와 Set의 원소로 불변 객체를 사용하는 것.
  • 불변 객체는 그 자체로 실패 원자성(failure atomicity)를 제공한다.

    💡 실패 원자성: 메서드에서 예외가 발생한 후에도 그 객체는 여전히 유효한 상태여야 한다는 성질.


✔️ 불변 객체 단점

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

📌 해결 방법 (2가지)

  1. 다단계 연산(multistep operation)을 정확히 예측할 수 있다면, package-private 가변 동반 클래스(companion class)를 둔다.
    ex) StringBuilder
  2. 다단계 연산을 예측할 수 없다면, 클래스를 public으로 제공한다.

✔️ 클래스 상속 방지하는 방법

클래스를 상속하지 못하게 하는 방법은 2가지 있다.

  1. final 클래스로 선언한다. (위의 예제 코드에서 사용한 방법)
  2. 모든 생성자private 혹은 package-private으로 만들고, public 정적 팩터리를 제공한다.

두 번째 방법을 사용한 예제 코드는 다음과 같다.

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);
    }
    ...
}
  • 생성자를 private으로 제공한다.
  • public 정적 팩터리인 valueOf 메서드를 제공한다.



📌 핵심 정리

  • getter가 있다고 해서 무조건 setter를 만들지는 말자.
  • 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
  • 단순한 값 객체는 항상 불변으로 만들자.
    무거운 값 객체도 불변으로 만들 수 있는지 고심하여야 한다.
  • 성능 때문에 불변으로 만들 수 없다면, 불변 클래스와 쌍을 이루는 가변 동반 클래스public 클래스로 제공하도록 하자.
  • 불변으로 만들 수 없는 클래스는 변경할 수 있는 부분을 최소한으로 줄이자.
    다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
    확실한 이유가 없다면 생성자와 정적 팩터리 외에는 그 어떤 초기화 메서드도 public으로 제공해서는 안 된다.
profile
백엔드 개발자

0개의 댓글