[ Java ] 불변 객체 Immutable Object

5tr1ker·2023년 4월 8일
0

Java

목록 보기
1/6
post-thumbnail

불변 객체란?

불변 객체 ( Immutuable Object ) 는 객체 생성 이후 내부의 상태가 변하지 않는 객체 입니다. 불변 객체는 read-only 메서드만을 제공하며, 객체의 내부 상태를 제공하는 메서드를 제공하지 않거나 방어적 복사 ( defensive - copy ) 를 통해 제공합니다. Java 의 대표적인 불변 객체로는 String이 있습니다.

Java의 String은 불변 클래스이기에 String 내부의 char 형 배열을 얻어 수정해도 변하지 않습니다. Java에서는 배열이나 , 객체 등의 참조 ( Reference ) 를 전달합니다. 그렇기 때문에 참조를 통해 값을 수정하면 내부의 상태가 변하기 때문에 내부를 복사하여 전달하고 있는데 이를 방어적 복사 ( defensive-copy) 라고 합니다.

불변 객체를 사용해야 하는 이유

Thread-Safe 하여 병렬 프로그래밍에 유용하며, 동기화를 고려하지 않아도 된다.

멀티 쓰레드 환경에서 동기화 문제가 발생하는 이유는 공유 자원을 동시에 쓰기 때문입니다. 하지만 공유 자원이 불변이라면 더이상 동기화를 고려하지 않아도 됩니다. 그 이유는 항상 동일한 값을 반환하기 때문에 안정성을 보장하며 , 동기화를 하지 않아 성능상의 이점도 가져다줍니다.

실패 원자적 ( Failure Atomic ) 메서드를 만들 수 있다.

가변 객체를 통해 작업을 하던 도중 예외가 발생하면 해당 객체는 불안정한 상태에 빠질 수 있고 , 불안정한 상태를 갖는 객체는 또 다른 오류를 유발할 수 있습니다.
하지만 불변 객체라면 어떠한 예외가 발생하여도 메서드 호출 전의 상태를 유지할 수 있으며, 예외가 발생해도 발생하지 않은 것 처럼 다음 로직을 처리할 수 있습니다.

Cache 나 Map 또는 Set 등의 요소로 활용이 적합하다.

만약 Cache나 Map , Set 등의 원소인 가변 객체가 변경됐다면 이를 갱신하는 등의 부가 작업이 필요할 것 입니다. 하지만 불변 객체라면 한번 데이터가 저장된 이후에 작업을 고려하지 않아도 됩니다.

부수 효과 ( Side Effect ) 를 피해 오류 가능성을 줄여줍니다.

부수 효과란 변수의 값이나 상태가 변하는 효과를 말합니다. 만약 객체의 수정자 ( Setter ) 를 통해 여려 객체들에서 값을 수정한다면 객체의 상태를 예측하기 어려울 것입니다. 바뀐 상태를 파악하기 위해 메서드들을 살펴보아야 하고, 이는 유지보수성을 떨어뜨립니다.

객체가 불변이라면 기본적으로 값의 수정이 불가능하므로 변경 가능성이 적으며, 객체의 생성과 사용이 상당히 제한됩니다. 그렇기 때문에 메서드들은 순수 함수들로 구성될 것이고 , 다른 메서드가 호출되어도 객체의 상태가 유지되기 때문에 안전하게 객체를 사용할 수 있습니다.

따라서 이러한 불변 객체는 오류의 가능성을 줄여 유지 보수성이 높은 코드를 작성하게 도와줍니다.

다른 사람이 작성한 함수를 예측할 수 있으며 안전하게 사용할 수 있습니다.

불변성은 협업 과정에서도 도움을 주는데, 불변성이 보장된 함수라면 값이 변하지 않음을 보장 하기 대문에 다른 사람이 작성한 함수를 위험없이 사용할 수 있습니다.

가비지 컬렉션의 성능을 높일 수 있다.

자바에서 객체를 생성하기 위해 객체를 가지는 또 다른 컨테이너 객체 ( ImmutableHolder ) 가 존재하는데, 불변의 객체 ( Object value ) 가 먼저 생성되어야 컨테이너 객체가 이를 참조할 수 있습니다.

  • Object 타입의 value 객체 생성
  • ImmutableHolder 타입의 컨테이너 객체 생성
  • ImmutableHolder 가 value 객체를 참조

이러한 점은 GC 가 수행될 때 , 컨테이너 객체 하위의 불변 객체를 Skip할 수 있게 합니다. 그 이유는 컨테이너 객체 ( ImmutableHolder ) 가 살아있다는 것은 하위의 불변 객체 ( Value ) 도 처음에 할당된 상태로 참조되고 있음을 의미하기 때문입니다.

결국 불변의 객체를 활용하면 가비지 컬렉터가 스캔해야 하는 객체의 수가 줄어 스캔해야 하는 메모리 영역과 빈도수가 줄어들 것이고 , GC 가 수행되더라도 지연 시간을 줄일 수 있습니다. 따라서 MutableHolder 보다는 ImmutableHolder를 사용하는 것이 좋습니다.

Holder의 값이 변하는 경우라면 MutableHolder를 사용하는게 낫지 않겠냐는 의구심을 가질 수 있지만 GC는 새롭게 생성된 객체는 대부분 금방 죽는다는 Weak Generational Hypothesis 가설을 맞추어 설계되었습니다. 따라서 GC 의 입장에서 생명 주기가 짧은 객체를 처리하는 것은 큰 문제가 아니며, MutableHolder의 값이 지속되어 old-to-young 참조가 일어나는 것이 더 큰 성능 저하를 일으킵니다.

Java에서 불변 객체를 생성하는 법

일반 변수에 대한 불변성

Java 에서는 불변성을 확보하기 위해 final 키워드를 제공하고 있습니다. 변수에 final 키워드를 붙이면 참조값을 변경하지 못하게 하여 불변석을 확보할 수 있습니다.

final String name = "Old";
name = "New";  // 컴파일 에러

객체에 대한 불변성

final 키워드는 객체 내부의 상태를 변경하지 못하게 하는건 아닙니다. 예를 들어 final 키워드로 선언된 List에는 add 메서드를 통해 객체의 상태가 변경되어도 문제가 없습니다. 때문에 참조에 의해 값이 변경될 수 있는 것을 막기 위해 불변 클래스로 만들어야 합니다.

Java 에서는 불변 객체를 생성하기 위해 다음과 같은 규칙을 따릅니다.

  • 클래스는 final 로 선언합니다.
  • 모든 클래스 변수를 private 와 final로 선언합니다.
  • 객체를 생성하기 위해 생성자 또는 정적 팩토리 메서드를 추가합니다.
  • 참조에 의해 변경가능성이 있는 경우 방어적 복사를 이용해 전달합니다.
public final class ImmutableClass {
    private final int age;
    private final String name;
    private final List<String> list;

    private ImmutableClass(int age, String name) {
        this.age = age;
        this.name = name;
        this.list = new ArrayList<>();
    }

	// 정적 팩토리 메서드
    public static ImmutableClass of(int age, String name) {
        return new ImmutableClass(age, name);
    }
    
    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

	// 방어적 복사
    public List<String> getList() {
        return Collections.unmodifiableList(list);
    }
    
}

위의 불변 클래스를 보면 내부 생성자를 만드는 대신에 정적 팩토리 메서드를 제공하고, 참조를 전달하여 클라이언트에 의해 수정가능성이 있는 list는 방어적 복사를 통해 제공하고 있습니다.

Java 에서는 생성자를 선언하지 않으면 기본 생성자가 생성되는데, 그러면 다른 클래스에서 해당 객체를 자유롭게 호출할 수 있습니다. 따라서 내부 생성자를 만드는 대신에 정적 팩토리 메서드를 통해 객체를 생성하는 것이 좋습니다.

배열이나 객체 , 컬렉션은 참조가 전달되어 수정 가능성이 있습니다. 따라서 참조를 통해 값이 변경될 수 있는 경우에 방어적 복사를 통해 값을 반환해야 합니다.

마지막으로 클래스의 변수에는 final을 , final이 불가능하다면 Setter를 최소화 합니다.

클래스들은 가변적이여야 하는 매우 타당한 이유가 있지 않는 한 반드시 불변으로 만들어야 한다. 만약 클래스를 불변으로 만드는 것이 불가능하다면, 가능한 변경 가능성을 최소화하라.
< Effective Java >

참고

참고 블로그 : https://mangkyu.tistory.com/131

profile
https://github.com/5tr1ker

0개의 댓글