Home [Effective Java] Item17. 변경 가능성을 최소화하라!
Post
Cancel

[Effective Java] Item17. 변경 가능성을 최소화하라!

Item17. 변경 가능성을 최소화하라!

불변 클래스. Immutable class

인스턴스 내부 값을 수정할 수 없는 클래스 불변 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 변경되지 않는다. 불변 클래스는 가변 클래스보다 설계, 구현, 사용이 쉽고, 오류가 생길 여지가 적어 안전하다.

불변 클래스 생성 규칙

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

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

    • 하위 클래스에서 객체의 상태를 변경하는 것을 막아준다.
    • 상속을 막는 대표적 방법에는 final로 클래스를 선언하는 것이 있다.
  • 모든 필드를 final로 선언한다.

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

    • 필드가 참조하는 가변 객체를 클라이언트에서 직접 접근해 수정하는 일을 막아준다.
  • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.

    • 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야 한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    public class Cafe {
        private final String name;
        private final Address address;
      
        public Cafe(String name, Address address) {
            this.name = name;
            this.address = address;	// 직접 참조
        }
        // getter
    }
    
    1
    2
    3
    4
    
    public class Address {
        private String region;
        // getter & setter
    }
    
    • 이 경우 Cafe 에서 Address 가변 객체를 직접 참조하고 있다.
    • 이는 Address 객체가 변경된다면 Cafe 객체도 변경되는 것을 의미한다.
    • 그렇기에 불변성이 깨진다.
    1
    2
    3
    4
    
    public Cafe(String name, Address address) {
        this.name = name;
        this.address = new Address(address.getRegion());
    }
    
    • 불변으로 만들기 위해 Address 객체를 새로 생성해 사용한다.

불변 클래스의 장단점

장점

  • 불변 객체는 단순하다.
    • 불변 객체는 생성 시점부터 파괴 시점까지 상태를 그대로 간직한다.
    • 모든 생성자가 불변식을 보장한다면 별다른 노력없이 영원히 불변으로 남는다.
    • 오류가 생길 여지가 적어 안전한 사용이 가능해진다.
  • 불변 객체는 근본적으로 thread-safe를 보장해 안전하며 따로 동기화가 필요 없다.
    • 어떤 스레드가 사용해도 절대 훼손되지 않는다.
    • 그렇기에 불변 객체는 안심하고 공유하는 것이 가능하다.
    • 또 한번 만든 불변 인스턴스를 최대한, 안전하게 재활용하는 것이 가능하다.
  • 불변 객체는 자유롭게 공유할 수 있고, 불변 객체끼리 내부 데이터 공유도 가능하다.
    • 방어적 복사가 필요 없다.
      • 아무리 복사해봐야 원본과 같으니 복사의 의미가 없다.
      • 그러니 불변 클래스는 clone 메서드나 복사 생성자를 제공하지 않는 것이 좋다.
    • 불변 객체의 필드가 가변 객체의 필드를 참조하더라도 final, private 제한자로 접근, 변경을 제한하기 때문에 여전히 불변을 유지한다.
  • 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
    • 값이 불변인 구성요소들로 이뤄진 객체라면 그 구조가 어떻든 불변식을 유지하기 수월하기 때문
  • 불변 객체는 그 자체로 실패 원자성을 제공한다.
    • 상태가 절대 변하지 않으니 잠깐이라도 불일치 상태에 빠질 가능성이 없다.

단점

  • 값이 다르면 반드시 독립된 객체가 필요하다는 것이다.
  • 즉, 새로운 인스턴스 생성 비용에 대한 문제도 고려해야 할 것이다.
  • 다단계 연산들을 예측하여 기본 기능으로 제공해 이와 같은 문제를 해결할 수 있다.
1
2
3
4
5
6
7
public static void main(String[] args) {
    String count = "";
    for (int i = 0; i <10; i++) {
        count +=String.valueOf(i);
        System.out.println(count);
    }
}
  • 위 코드는 String 인스턴스를 계속해서 생성하고 출력한다.
    • String의 가변 동반 클래스인 StringBuilder를 사용해 이러한 문제를 해결할 수 있다.

불변 클래스 설계 방법

  • 앞서 살펴본 것처럼 final 클래스로 만들어 자신을 상속하지 못하게 막는 방법

    • 이 규칙이 약간 과한 감이 있어, 성능을 위해 약간 완화한 경우도 있다.
      • 계산 비용이 큰 값을 나중에 계산하여 final이 아닌 필드에 캐시해놓고 사용하는 방법
      • 똑같은 값 요청 시 캐시해둔 값을 반환해 계산 비용을 줄인다.
      • 순전히 그 객체가 불변이기 때문에 몇 번을 계산해도 항상 같은 결과를 반환함이 보장되어 이러한 방법을 사용할 수 있다.
  • 모든 생성자를 private 혹은 package-private으로 만들고, public 정적 팩터리를 제공하는 방법

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    // 생성자 대신 정적 팩터리를 사용한 불변 클래스
    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);
        }
      	//.. 생략
    }
    

정리

  • Getter가 있다고 해서 무조건 Setter를 만들지는 말자.
    • 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
  • 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소화하자.
    • 객체가 가질 수 있는 상태의 수를 줄이면 예측이 쉬워지고, 오류 가능성 낮아진다.
    • 꼭 변경 필요한 필드 빼고는 final로 선언하자.
    • 아이템 15 내용을 통해, 다른 이유 없다면 모든 필드는 private final로 선언한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
    • 확실한 이유 없이 생성자와 정적 팩터리 외의 어떤 초기화 메서드도 public으로 제공되어서는 안된다.
This post is licensed under younghwani by the author.

[Effective Java] Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라!

[Effective Java] Item18. 상속보다는 컴포지션을 사용하라!

Comments powered by Disqus.