JAVA/이펙티브자바

이펙티브자바 Item17. 변경 가능성을 최소화 하라

ghyeong 2024. 10. 7. 23:32
반응형

불변클래스란?

  • 인스턴스의 내부 값을 수정할 수 없는 클래스
  • 불변클래스 예시 → String, 박싱 클래스, BigInterger, BigDecimal
    • 가변클래스보다 설계하고 구현하고 사용하기 쉽다
    • 오류가 생길 여지가 적고 안전하다

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

  • 객체의 상태를 변경하는 메서드를 제공하지 않는다
    • setter가 존재하지 않아야한다
  • 클래스를 확장할 수 없도록 한다
    • class의 선언을 final로 선언하는 방법 존재
    • 생성자를 private으로 막아 상속을 막는다**> 정적 팩토리 메서드 사용**
  • 모든필드를 private로 선언한다
    • 시스템이 강제하는 수단을 이용해서 설계자의 의도를 명확히 드러내도록 한다.
  • 모든필드를 final로 선언한다
    • 클라이언트에서 직접 접근해 수정하는 것을 막아준다.
    • public final로 선언만으로도 불변 객체가 되지만, 다음 릴리스에서 내부 표현을 바꾸지 못한다
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다
    • 방어적 복사를 수행해야 한다.
    • 방어적 복사 VS 얕은 복사
    방어적 복사방어적 복사를 하게 되면, 복사한 외부의 객체를 변경해도 원본 내부 객체가 변경되지 않는다!원본 객체를 복사할 때, 새로운 객체를 만들지만 원본 객체의 '주소 값'을 참조하는 복사이다.(call-by-reference와 유사한 개념)불변 복소수 클래스
  • public class Lotto{ private final List<Integer> numbers; public Lotto(List<Integer> numbers){ this.numbers = List.copyOf(numbers); } // 방어적 복사 public List<Integer> getNumbers(){ return Collection.unmodifiableList(numbers); } // 방어적 복사 }
  • 따라서 원본이나 복사한 객체나 변경이 되면 서로 영향을 미친다.
  • 얕은 복사
  • 내부의 객체를 반환할 때, 객체의 복사본을 만들어서 반환하는 것을 방어적 복사라고 한다.
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;

        return Double.compare(c.re, re) == 0
                && Double.compare(c.im, im);
    }
    
    @Override public int hashCode(){
        return 31 * Double.hashCode(re) + Double.hashCode(im);
    }
    
    @Override public String toString(){
        return "(" + re + " + " + im + "i)";
    }
    
}
  
  • 사칙 연산 메서드들이 인스턴스 자신은 수정하지 않고 새로운 Complex 인스턴스를 만들어 반환한다. → 피연산자 자체는 그대로인 프로그래밍 패턴을 함수형 프로그래밍이라고 한다.
  • 절차적 혹은 명령형 프로그래밍에서는 메서드에서 피연산자인 자신을 수정해 자신의 상태가 변한다.
  • 함수형 프로그래밍은 코드에 불변이 되는 영역의 비율이 높아지는 장점을 가진다
반응형

불변객체를 사용의 장점

  • 불변객체는 근본적으로 스레드 안전하여 따로 동기화할 필요가 없다
    • 안심하고 공유가능
  • 캐싱기능을 사용 수월 (재사용 가능 → 재사용할때 마다 같은 값이 나오기 때문에)
  • 방어적 복사가 필요없다
    • 복사자체가 의미가 없다(복사를 해도 원본이랑 같기 때문에 굳이 clone 메서드나 복사 생성자를 만들 필요가 없다)
  • 자유롭게 공유할 수 있으며, 불변 객체끼리는 내부 데이터를 공유할 수 있다
  • 상태가 변하지 않기 때문에 불일치 상태에 빠질 가능성이 없다.

불변객체 사용의 단점

  • 값이 다르면 반드시 독립된 객체로 만들어야 한다.
    • 값의 가짓수가 많다면 많은 비용이 들게된다.
      • EX) BigInteger에서 비트 하나를 바꿔야한다면 계속해서 새로운 인스턴스를 만들게 된다.
    • 그럴 경우엔 가변 동반 클래스를 제공하도록 한다.

가변동반 클래스란?

  • 불변 클래스와 거의 동일한 기능을 가지고 있지만, 가변적인 클래스를 가변 동반 클래스라고 한다.
  • 가변 동반 클래스는 주로 불변 클래스가 비즈니스 로직 연산 등 시간 복잡도가 높은 연산시 불필요한 클래스 생성을 막기위해 내부적으로 사용한다.
  • 대표적으로 불변 클래스인 String은 가변 동반 클래스인 StringBuilder가 있다.

정리

  • Getter가 있다고해서 무조건 Setter를 만들지는 말자(특히 값(Value) 객체(Object))
  • 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다
    • 장점이 무수히많고, 단점은 잠재적 성능저하 하나 뿐이라
  • 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자
    • 변경해야 할 필드를 뺀 나머지 모두를 final로 선언
  • 다른 합당한 이유가 없다면 모든 필드는 private final이여야 한다
  • 생성자는 불변식 설정이 모두 완료된(초기화)가 완벽히 끝난 상태의 객체를 생성해야 한다
    • 생성자와 정적 팩터리 외에는 그 어떤 초기화 메서드도 public으로 제공해서는 안된다
반응형