ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 불변객체란 무엇인가?
    Java 2022. 5. 29. 19:07

    불변 객체란?

    객체 생성 이후 내부의 상태가 변하지 않는 객체입니다.

    불변 객체는 read-only 메서드만을 제공하며 객체의 내부 상태를 제공하는 메서드를 제공하지 않거나 방어적 복사를 통해 제공합니다.

    Java 대표적인 불변 객체로는 String이 있습니다.

     

    방어적 복사란?

    예를 들어 Java의 String은 불변 객체 이기 때문에 String의  내부의 char형 배열을 얻어 수정을 해도 반영이 되지 않습니다.

    String은 객체이기 때문에 참조를 통해 값을 수정하면 내부의 상태가 변하기 때문에 내부를 복사하여 전달하는 것을 방어적 복사라고 합니다.

     

    실제로 다음과 같이 name은 변경해도 변하지 않습니다.

    String name = "Old";
    char changed = name.toCharArray()[0] = 'C';
    System.out.println(changed); //C 출력
    System.out.println(name); //Old 출력

     

    불변 객체를 사용하는 이유는?

    Thread-Safe 하기 때문에 동기화 문제를 고려하지 않아도 됩니다.

    멀티 스레드 환경에서 동기화 문제가 발생하는 이유는 공유 자원에 동시에 쓰기 때문으로 공유 자원이 불변이라면 동기화 문제를 고려하지 않아도 됩니다.

     

    실패 원자적인 메서드를 만들 수 있다.

    가변 객체를 통해 작업하는 도중 예외가 발생하면 객체가 불안정한 상태에 빠질 수 있습니다.

    불안정한 상태의 객체는 에러를 유발할 가능성이 있습니다.

    하지만 불변 객체는 예외가 발생하더라도 메서드 호출 전의 상태를 그대로 유지하며 오류가 발생하지 않은 것처럼 다음 로직을 처리할 수 있습니다.

     

    캐시, Map, Set 등의 요소로 활용하기 좋다.

    Map, Set 등의 원소인 가변 객체가 변경되었다면 이를 갱신하는 등의 부가 작업이 필요합니다.

    하지만 불변 객체는 한번 저장된 이후에 변경되지 않기 때문에 사용하는데 용이하게 작용될 것입니다.

     

    Side Effect를 피해 오류 가능성을 최소화합니다.

    Setter를 사용하여 여러 객체들에서 값을 변경하면 객체의 상태를 예측하기 어려워집니다.

    즉, 이 객체가 어디서 값이 바뀐 것인지 추적하기 힘들기 때문에 유지보수성이 떨어지게 됩니다.

    만약 객체가 불변 객체라면 부수효과가 없기 때문에 유지보수성이 높은 코드를 작성하도록 도와줍니다.

     

    협업에서 도움을 줍니다.

    만약 불변성이 보장된 함수라면 다른 개발자가 작성한 함수를 위험 없이 이용할 수 있습니다.

    다른 개발자가 작성한 메서드를 호출해도 값이 변하지 않음을 보장받을 수 있기 때문입니다.

     

    GC의 성능을 높입니다.

    GC의 특징 중 하나로 Heap영역은 Young Generation과 Old Generation으로 나뉘어 있습니다.

    또한 이를 통해 Minor GC, Major GC가 발생하게 됩니다.

    이렇게 영역이 나뉜 이유는 새롭게 생성된 객체는 대부분 금방 죽는다는 가설에 맞추어 설계되어 있습니다.

    즉, 객체의 값을 변경하면서 계속 참조되어 Old Generation에서 GC가 일어나는 것보다 객체의 값이 변경되더라도 새로운 불변 객체에 값을 할당해서 사용한다면 Young Generation에서 GC가 일어나서 더 빠르게 처리할 수 있습니다.

     

     

    불변객체 만들기

    1. 모든 필드에 final 설정

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

    2. 필드에 참조 타입이 있을 경우, 해당 객체도 불변성을 보장해야 한다.

    public class Car {
    
        private final String name;
    
        private final Position position;
    
        public Car(String name, Position position) {
            this.name = name;
            this.position = position;
        }
    
        // 필요하다면 getter만 사용. setter는 금지
    }

    불변 객체로 만들어진 것처럼 보이지만 만약 Position이 변경될 가능성이 있다면 이는 불변 객체가 될 수 없습니다.

     

    3. 필드에 컬렉션이 존재할 경우, 생성자 및 getter에 대해 방어적 복사를 수행

    public class Car {
    
        private final String name;
    
        private final int position;
    
        private final List<Integer> monthlyMileages;
    
        public Car(String name, int position, List<Integer> monthlyMileages) {
            this.name = name;
            this.position = position;
            this.monthlyMileages = monthlyMileages;
        }
    
        // 필요하다면 getter만 사용. setter는 금지
    }

    마찬가지로 컬렉션을 사용하는 경우에도 컬렉션은 다음과 같이 변경될 수 있습니다.

    public class Main {
    
        public static void main(String[] args) {
            List<Integer> monthlyMileages = new ArrayList<>(Arrays.asList(10, 15, 20, 10, 15));
            Car car = new Car("jayon", 10, monthlyMileages);
            System.out.println(car);
    
            monthlyMileages.add(1000);
            System.out.println(car);
        }
    }

     

    따라서 생성자에서 그냥 monthlyMileages를 넘기는 것이 아니라 방어적 복사를 거치고 넘겨주어야 합니다.

    ublic class Car {
    
        private final String name;
    
        private final int position;
    
        private final List<Integer> monthlyMileages;
    
        public Car(String name, int position, List<Integer> monthlyMileages) {
            this.name = name;
            this.position = position;
            this.monthlyMileages = new ArrayList<>(monthlyMileages);
        }
    
        // 필요하다면 getter만 사용. setter는 금지
    }

    하지만 여기서 상황에 따라 getter으로 monthlyMileages를 넘겨주어야 하는 경우에도 다른 곳에서 add()나 remove()가 일어나면 가변적으로 변할 수 있습니다.

     public List<Integer> getMonthlyMileages() {
            return new ArrayList<>(monthlyMileages);
        }

    따라서 이 또한 위와 같은 코드를 통하여 새로운 ArrayList를 생성하여 전달하는 방식을 사용합니다. (방어적 복사)

    Collections.unmodifiableList()를 사용하는 경우도 있습니다.

     

     

     

     

    결론

    클래스들은 가변적이어야 하는 매우 타당한 이유가 있지 않는 한 반드시 불변으로 만들어야 한다.

    그것이 불가능하다면 변경 가능성을 최소화해야 한다.

     

     

     

    출처

    https://mangkyu.tistory.com/131

    https://steady-coding.tistory.com/559

     

    [Java] 가변 객체 vs 불변 객체

    java-study에서 스터디를 진행하고 있습니다. 가변 객체 (Mutable Object) 가변 객체는 Java에서 Class의 인스턴스가 생성된 이후에 내부 상태가 변경 가능한 객체이다. 가변 객체는 멀티 스레드 환경에서

    steady-coding.tistory.com

     

    'Java' 카테고리의 다른 글

    Java Lazy Evaluation이란?  (0) 2022.07.24
    책임 연쇄 패턴이란?  (0) 2022.07.19
    Java에 원시타입(primitive type)이 존재하는 이유  (0) 2022.05.28
    전략 패턴이란?  (0) 2022.05.25
    Java는 왜 Pure OOP가 아닐까?  (0) 2022.05.10

    댓글

Designed by Tistory.