Skip to content

Latest commit

 

History

History
80 lines (62 loc) · 5.47 KB

item17.md

File metadata and controls

80 lines (62 loc) · 5.47 KB

[Item 17] 변경 가능성을 최소화하라

Immutable class (불변 클래스)

  • 불변 클래스란 그 인스턴스 내부 값을 수정할 수 없는 클래스
  • 불변 인스턴스에 간직된 정보는 객체가 파괴되는 순간까지 절대 달라지지 않는다.
    • 예 : String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal
  • 불변 클래스는 가변 클래스보다 설계,구현 하기가 쉽다. 또한 오류가 생길 여지도 적고 훨씬 안전하다.

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

  1. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
    • ex: setter method
  2. 클래스를 확장할 수 없도록 한다.  (extend x)
  3. 모든 필드를 final로 선언한다. 시스템이 강제하는 수단을 이용해 설계자의 의도를 드러내는 방법이다.
  4. 모든 필드를 private로 선언한다. 필드가 참조하는 가변 객체를 클라이언트에서 직접 접근해 수정하는 일을 막아준다.
  5. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야 한다. 그리고 접근자 메서드가 해당 필드를 그대로 반환해서도 안 되며, 방어적 복사를 수행해야 한다.

불변 객체 장점

  1. 단순하다
  • 불변 객체는 단순하다. 생성 시점의 상태를 파괴될 때까지 간직한다.
  • 모든 생성자가 클래스 불변식(class invariant)을 보장한다면 별다른 노력을 기울이지 않아도 영원히 불변으로 남는다. 반면 가변 객체는 복잡한 상태에 놓일 수도 있다.
  1. 자유롭게 공유 가능
  • 불변 객체는 스레드 안전 : 동기화할 필요 없다
    • 변경되지 않으므로 여러 스레드에서 접근하더라도 훼손되지 않는다.
  • 불변 객체는 안심하고 공유 가능 : 어떤 스레드도 다른 스레드에 영향을 줄 수 없다.
  • 가장 쉬운 불변 클래스의 인스턴스 재활용 방법은 자주 쓰이는 값들을 상수(public static final)로 제공하는 것이다.
  • 불변 클래스는 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩터리를 제공할 수 있다.
  • 이런 정적 팩터리를 사용하면 여러 클라이언트가 인스턴스를 공유하여 메모리 사용량과 가비지 컬렉션 비용이 줄어든다.
  • 새로운 클래스를 설계할 때 public 생성자 대신 정적 팩터리를 만들어두면, 클라이언트를 수정하지 않고도 필요에 따라 캐시 기능을 나중에 덧붙일 수 있다.
  • 불변 객체는 방어적 복사가 필요 없다.
    • 불변 객체를 복사해도 항상 원본이랑 같기 때문에 의미가 없다.
  • 불변 객체는 그 자체로 실패 원자성을 제공한다.
    • 실패 원자성(failure atomicity) : '메서드에서 예외가 발생한 후에도 그 객체는 여전히 (메서드 호출 전과 똑같은) 유효한 상태를 가진다’

불변 객체 단점

  • 불변 객체는 값이 다르다면 반드시 독립된 객체로 만들어야 한다.
  • 특히 인스턴스 생성의 비용이 큰 경우(ex 100만 비트 짜리 BigInteger, 생명 주기가 긴 객체 등) 이 문제가 두드러질 수 있다.

대책

  1. 인스턴스 캐싱
    • 인스턴스 캐싱을 통해 인스턴스를 새로 생성하지 않고 재사용하여 비용을 줄인다.
    • 자바의 대부분의 기본값 wrapper클래스에서 사용되고 있는 방식이다. (Boolean, Integer, String...)
  2. 다단계 연산을 예측하여 기본 기능으로 제공
    • 인스턴스를 생성하는데 여러 단계를 거치는데, 이 단계들을 예측하여 기능으로 제공
    • ex) 불변 객체 String은 가변 동반 클래스인 StringBuilder를 제공
    • StringBuilder에서는 String 인스턴스를 생성하는 여러 단계를 쪼개어 가변으로 객체를 생성할 수 있는 기능을 제공한다. (append메서드를 예로 들 수 있다.)
public static void main(String[] args) {
    String count = "";
    for (int i = 0; i <10; i++) {
        count +=String.valueOf(i);
        System.out.println(count);
    }
}

다음과 같은 경우 String 의 인스턴스를 계속해서 만든다.

따라서 다음과 같은 경우 StringBuilder 가변 동반 클래스를 제공한다.

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

  1. final 클래스로 선언
  2. 모든 생성자를 private 혹은 package-private 으로 만들고 정적팩터리을 제공하는 방법이 더 유연한 방법이다.

정리

  • getter 메서드가 있다고 해서 무조건 setter 를 만들지는 말자.
    • 클래스는 꼭필요한 경우가 아니라면 불변이어야 한다.
  • 불변으로 만들 수없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자.
    • 꼭 변경해야 할 필드를 뺀 나머지는 final로 선언
    • 아이템 15의 조언과 종합하자면 다른 합당한 이유가 없다면 모든 필드는 private final 이어야한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야한다.
    • 확실한 이유가 없다면 생성자와 정적 팩터리 외에는 그 어떤 초기화 메서드도 public 으로 제공 하면 안된다.