Effective Java | #17. 변경 가능성을 최소화하라

보람·2022년 5월 7일
0

Effective-Java

목록 보기
17/25

불변클래스

  • 그 인스턴스의 내부 값을 수정할 수 없는 클래스
  • 불변 인스턴스 내 정보는 객체가 파괴되는 순간까지 절대로 달라지지 않음(단순함)
  • String, 기본 타입의 박싱 클래스, BigInteger, BigDecimal 이 속함

불변클래스 생성 규칙

책에서는 다섯 가지 규칙을 따르면 된다고 말한다.

1. 객체의 상태를 변경하는 메서드(변경자)를 제공X

2. 클래스를 확장할 수 없도록 한다.

3. 모든 필드를 final로 선언

  • final을 통해 설계자의 의도를 명확히 드러낸다. (얘 수정하지마!!!라고 강조)

4. 모든 필드를 private으로 선언

  • 클라이언트가 값을 직접 접근하여 수정하는 일을 막음
  • public final도 사용 가능하지만 해당 클래스 내에서도 변경할 수 없으므로 권하지는 않음(item-15,item-16)

5. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.

참조라는 것은 해당 값을 바꿀 수가 있쥬?

  • 가변 객체를 참조하는 필드가 있다면 클라이언트가 그 객체의 참조를 얻을 수 없도록 한다.
  • 생성자, 접근자, readObject 메서드(item-88) 모두에서 방어적 복사를 수행 할 것

요약 : private final 추천

/**
 * 불변(final) 복소수 클래스
 */
public final class Complex {
    private final double re;
    private final double im;
    
    /**
     * public 생성자
     * @param re
     * @param im
     */
    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
    
    /**
     * 더하기 메서드
     * @param c
     * @return
     */
    public Complex plus(Complex c) {
        /**
         * 객체의 값을 수정하지 않고 새로운 Complex 인스턴스를 만들어(new) 반환(return)
         * 원하는 값을 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴 -> 함수형 프로그래밍
         * 객체의 값이 변경되지 X
         * 이런 방식으로 프로그래밍을 하면 불변이 되는 영역의 비율이 높아지는 장점을 누릴 수 있다
         */
        return new Complex(re + c.re, im + c.im);
    }
}

불변객체는 생성된 시점의 상태를 파괴될 때까지 그대로 간직하는데 이로 인해 얻을 수 있는 이점은 다음과 같다.

불변객체 장점

  • 단순하다.
  • 근본적으로 스레드 안전하여 따로 동기화할 필요 없다
    • 여러 스레드에 동시에 사용돼도 변경X
    • 스레드 안전으로 안심하고 공유 가능
    • 생성된 인스턴스 중 자주 사용되는 값 재활용 추천 -> public static final 로 제공 (코틀린에서는 const val)
  • 불변 객체끼리는 내부 데이터 공유 가능
    • 복사하는 것이 아닌 그 데이터 그대로를 사용하는 것
  • 객체 생성시 다른 불변 객체들을 구성요소로 사용시 이점이 많음 ex) 맵의 혹은 Set의 원소로 사용
    • 키나 원소가 변경되지 않기 때문에 값이 틀어질 걱정 X
  • 그 자체로 실패 원자성(실패되더라도 값 불일치 X) 제공

공유가 가능한 불변객체

  • 자주 사용되는 인스턴스를 캐싱하여 인스턴스 중복 생성을 방지하는 정적 팩터리 (item-1)제공 가능
  • 방어적 복사(item-50) 필요 X
    • String 복사 생성자는 이를 이해하지 못한 시기에 작성됐기 때문에 사용하지 않는 것이 좋음(item-6)

모든것이 좋아보이는 불변객체.. 단점도 있쥬..

불변객체 단점

값이 다르면 반드시 독립된 객체로 만들어야 한다

  • 값의 가짓수가 많으면 그 값을 만드는데 큰 비용이 들지만 변할 수 없는 불변객체는 새로운 객체를 생성해야 함
BigInteger moby = ...; //불변 객체 
moby = moby.flipBit(0); 

BigSet moby = ...; //가변 객체
moby.flip(0); //=이 없는 것 보이시쥬?
  • 원하는 객체를 완성하기까지의 단계가 복잡하다면 ..
    1. 다단계 연산을 예측하여 기본 기능으로 제공할 것
    2. 정확한 연산 예측이 가능하면 package-private의 가변 동반 클래스를, 그렇지 않다면 public으로 제공 ex) StringStringBuilder

위에서는 불변클래스 설계시 final 클래스를 사용하라고 하였지만 조금 더 유연한 방법이 한가지 남았다.🌝

유연한 불변 클래스

  • 모든 생성자를 private or package-private 로 지정
  • public 정적 팩터리 제공(item-1)
/**
 * 복소수 클래스
 */
public class Complex { //final 없는거 보이시쥬?
    private final double re;
    private final double im;
    
    /**
     * private 생성자 -> 외부에서 해당 생성자 접근 X 
     * @param re
     * @param im
     */
    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
    
    // public 정적 팩터리
    public static Complex valueOf(double re, double im) {
        //이런식으로 할땐 final이 아닌 클래스임에도 접근할 수 있는 내부 값이 없기 때문에(private) 사실상 final이 된다.
        return new Complex(re, im);
    }
}

정리

  • 모든 객체를 수정할 수 없다는 것은 과한 부분이 있기에 어떤 메서드도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다. 로 완화 가능
  • 계산 비용이 큰 값이라면 사용시 캐싱해서 재요청시 반환
    • PhoneNumber의 hashCode 메서드(item-11), 지연 초기화(item-83)의 String
  • 꼭 필요한 경우를 제외하곤 불변
    • getter & setter 가 꼭 짝일 필요 X
  • 불변클래스일 수 없더라도 변경할 수 있는 부분 최소화
  • 다른 합당한 이유가 없다면 모든 필드는 private final
끝내면서...

kotlin-in-action 책에서는
코틀린의 기본 가시성이 final이라고 했는데 자바의 이런 특성으로 코틀린에서는 이를 변경했겠구나 라는 생각이 들었다.

profile
백엔드 개발자

0개의 댓글