[Effective Java] 아이템 17 - 변경 가능성을 최소화하라

HyeBin, Park·2022년 6월 4일
0

Effective Java Study

목록 보기
13/20
post-thumbnail

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

🥦 불변 클래스

  • 인스턴스의 내부 값을 수정할 수 없는 클래스객체가 파괴되는 순간까지 절대 값이 달라지지 않는다. => 단순하다.

  • 가변 클래스보다 설계, 구현, 사용이 쉬우며 오류가 생길 여지도 적고 안전하다.

  • String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal ...

🐾 클래스를 불변으로 만드는 규칙

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

  2. 클래스를 확장할 수 없도록한다.
    => 하위 클래스에서 객체의 상태를 변하게 만드는 사태를 막아준다. 상속을 막는 방법으로 클래스를 final로 선언한다.

  3. 모든 필드를 final로 선언한다.
    => 시스템이 강제하는 수단을 이용해 설계자의 의도를 명확히 드러내는 방법

  4. 모든 필드를 private으로 선언한다.
    => 필드가 참조하는 가변 객체를 클라이언트에서 직접 접근해 수정하는 일을 막아준다.
    => public final로 선언해도 불변 객체가 되지만, 이렇게 하면 다음 릴리스에서 내부 표현을 바꾸지 못하므로 권하지는 않는다.

  5. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
    => 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야햔다.
    => 접근자 메서드가 그 필드를 그대로 반환해서도 안 된다. 생성자, 접근자, 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 divdedBy(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) == 0;
    }

    @Override
    public int hashCode(){
        return 31 * Double.hashCode(re) + Double.hashCode(im);
    }

    @Override
    public String toString(){
        return "("+re+" + "+im+"i)";
    }
}
  • 함수형 프로그래밍 : 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴
    => 예) 위의 코드에 새로운 인스턴스를 만들어 반환하는 모습
  • 코드에서 불변이 되는 영역의 비율이 높아지는 장점이 있다.
  • 절차적 혹은 명령형 프로그래밍에서는 메서드에서 피연산자인 자신을 수정해 자신의 상태가 변하게 된다.
  • 메서드 이름으로 동사 대신 전치사를 사용하여 해당 메서드가 객체의 값을 변경하지 않는다는 사실을 강조한다.
    => 해당 내용의 잘못된 예시 java 의 BigInteger (BigDecimal도 해당)

🌟 불변객체의 장점

  • 모든 생성자가 불변식을 보장한다면 프로그래머가 노력을 들이지 않아도 불변으로 남는다.
    => 가변객체의 경우 상태 전이를 정밀하게 문서로 남겨놓지 않으면 믿고 사용하기 어렵다.

  • 불변 객체는 근본적으로 스레드 안전하여 따로 동기화할 필요 없다.
    => 여러 스레드가 동시에 사용해도 절대 훼손 x, 안심하고 공유 가능 !

  • 따라서 한 번 만든 인스턴스를 최대한 재활용하기를 권한다.
    => 가장 쉬운 방법은 인스턴스를 상수로 제공하는 것 -> 정적 팩터리 사용하기

  • 방어적 복사가 필요없다. 아무리 복사해봐야 원본과 같으므로 복사 자체가 의미가 없다.
    => clone 메서드나 복사 생성자를 제공하지 않는게 좋다.

  • 불변 객체끼리는 내부 데이터를 공유할 수 있다.

    => BigInteger 클래스는 내부에서 값의 부호와 크기를 따로 표현한다. 부호는 int , 크기에는 int 배열을 사용한다.
    => negate메서드는 크기가 같고 부호만 반대인 새로운 BigInteger를 생성하는데 배열은 가변이지만 복사하지 않고 원본 인스턴스와 공유해도된다. 새로운 BigInteger 인스턴스도 원본 인스턴스가 가리키는 내부 배열을 그대로 가리킨다.

  • map의 키와 set의 원소로 쓰기에 좋다. 불변식이 허물어질 걱정을 하지 않아도 된다.

  • 불변 객체는 그 자체로 실패 원자성을 제공한다. 상태가 절대 변하지 않으니 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
    => 실패 원자성 ? 메서드에서 예외가 발생한 후에도 그 객체는 메서드 호출 전과 똑같은 유효한 상태여야 한다는 성질

🐏 불변 클래스 단점

  • 값이 다르면 반드시 독립적 객체로 만들어야 한다는 것
    => 값의 가짓수가 많으면 큰 비용을 치뤄야한다.

🤖 불변 클래스를 만드는 설계 방법

  • final 클래스로 선언하기

  • 모든 생성자를 private 혹은 package-private로 만들고 public 정적 팩터리를 제공하는 방법
    => 다른 패키지에서 이 클래스를 확장하는 것이 불가능 하다.
    => 정적 팩터리 방식은 유연성을 제공하고 다음 릴리스에서 객체 캐싱 기능을 추가해 성능을 끌어올릴 수도 있다.

🧸 정리하기

  • getter가 있다고 해서 무조건 setter를 만들지 말자
  • 단순한 값 객체는 항상 불변으로 만들자
  • 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.

0개의 댓글