불변 클래스 : 인스턴스의 내부 값을 수정할 수 없는 클래스

클래스를 불변으로 설계하는 이유 : 가변 클래스보다 설계하고 구현하고 사용하기 쉽고 오류가 생길 여지가 적어 안전하다.

 

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

  1. 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
    • setter 메서드를 제공하지 않아야한다. 객체의 상태는 생성 시점에만 설정되고, 이후에는 변경될 수 없다.
  2. 클래스를 확장할 수 없도록 한다.
    • 상속을 통해 하위 클래스에서 부주의하게 객체의 상태를 변경하는 사태를 막아준다.
  3. 모든 필드를 final로 선언한다.
    • 필드가 생성자에서 한 번만 할당될 수 있고, 이후에는 그 값이 변경될 수 없다.
  4. 모든 필드를 private으로 선언한다.
    • private으로 선언함으로써 클래스 외부에서 직접 접근할 수 없게한다.
  5. 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
    • 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야 한다. 가변 객체를 참조하는경우 해당 참조를 직접 노출하지 않고 방어적 복사를 통해 반환해야 한다.

이 다섯가지 규칙을 모두 활용해 만든 예시 코드이다 : 

public final class ImmutableClass {
    private final String name; // 3. 모든 필드는 final
    private final int value;
    private final List<String> mutableList; // 가변 컴포넌트

    // 생성자에서 모든 필드를 초기화
    public ImmutableClass(String name, int value, List<String> mutableList) {
        this.name = name;
        this.value = value;
        // 5. 내부 가변 컴포넌트의 방어적 복사본 생성
        this.mutableList = new ArrayList<>(mutableList);
    }

    // 1. 변경자 메서드 없음

    // 접근자 메서드는 필드의 값만 반환하고, 가변 필드의 경우 방어적 복사본을 반환
    public String getName() {
        return name;
    }

    public int getValue() {
        return value;
    }

    public List<String> getMutableList() {
        // 가변 필드에 대한 방어적 복사본 반환
        return new ArrayList<>(mutableList);
    }
}

 

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

똑같은 기능의 객체를 매번 생성하기 보다는 객체 하나를 재사용하는 편이 나을 때가 많다. 

 

예시 : 문자열 인스턴스의 불필요한 생성 피하기

비효율적인 예
public String repeat(String str, int count) {
    String result = "";
    for (int i = 0; i < count; i++) {
        result += str; // 매 반복마다 새로운 String 객체 생성
    }
    return result;
}

효율적인 예
public String repeat(String str, int count) {
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < count; i++) {
        result.append(str); // 동일한 StringBuilder 객체를 재사용
    }
    return result.toString();
}

자바에서 String 객체는 불변이다. 불변이란 한 번 생성된 String 객체의 내용이 변경될 수 없다는뜻이다.  비 효율적인 예에서 문자열을 반복적으로 연결할 때마다 새로운 String 객체가 생성되어 메모리 사용량이 증가하고 성능이 저하된다.

 

예시 : 불변 객체의 재사용

비효율적인 예
Boolean trueValue = new Boolean(true); // 불필요한 Boolean 객체 생성
Boolean falseValue = new Boolean(false);

효율적인 예
Boolean trueValue = Boolean.valueOf(true); // Boolean 객체 재사용
Boolean falseValue = Boolean.valueOf(false);

불변 객체는 내부 상태가 변경되지 않으므로 같은 값을 가지는 인스턴스를 여러번 생성할 필요가 없다. 

예를 들어, Boolean.valueOf(boolean) 메소드는 Boolean.TRUE 또는 Boolean.FALSE를 반환하여, 같은 Boolean 객체를 재사용할 수 있게 한다.

 

결론 : 기존 객체를 재사용해야 한다면 새로운 객체를 만들지 마라.

+ Recent posts