ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Java] 아이템17: 변경 가능성을 최소화하라
    Language/Java 2022. 6. 29. 16:00

     

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

    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으로 제공해서는 안 된다.

     

     

     

     

     

    728x90

    댓글

Designed by Tistory.