ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Mutable과 Immutable
    Java 2022. 1. 4. 23:25

    자바의 객체는 기본적으로 힙 영역에 할당되고 스택 영역에 참조값을 갖는 참조 타입 변수를 통해 데이터에 접근한다. 이러한 객체는 Mutable(가변) 객체Immutable(불변) 객체로 나눌 수 있다.

    Immutable Object

    불변 객체는 데이터 변경이 불가능한 객체를 말한다. 단순하게 생각하면 final 키워드처럼 초기화 이후에 값을 변경할 수 없다고 생각할 수 있다. 하지만 불변 객체는 객체의 데이터 수정이 아예 불가능한 것이 아니라 힙 영역에 저장된 값을 수정할 수 없는 것이다.

    불변 객체의 종류로는 String, Boolean, Integer, Float, Long, Double 등이 있다. 이는 String을 제외하곤 원시 타입의 래퍼 타입이다.

    어 ❔ 저 타입의 객체는 수정할 수 있는데 ❔❔

    맞다. 우리가 보기에는 위에서 말한 불변 객체의 수정이 가능하다. 하지만 위에서 말한 것과 같이 힙 영역에 저장된 값이 수정된 것이 아니라 힙 영역에 새로운 객체가 생성된 것이다.

    Integer i = 1;
    i = 3;

    위와 같이 우리는 i라는 Integer 타입 변수를 1에서 3으로 변경할 수 있다.

    위 그림처럼 i가 1에서 3으로 변경된 것으로 볼 수 있지만 실제로는 객체의 값이 변경된 것이 아니라 새로운 객체를 생성하고 이 객체에 대한 참조값을 변경한 것이다. 기존 1로 할당되었던 객체는 Garbage로 남아있다가 GC(garbage collection)에 의해 사라지게 된다.

    불변 객체를 만드는 방법 ❗

    final 키워드

    자바에서 불변성을 확보할 수 있도록 final 키워드를 제공하고 있다. 자바에서 변수들은 기본적으로 가변적인데, 변수에 final 키워드를 붙이면 참조값을 변경 못하도록 해 불변성을 확보할 수 있다.

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

    final이 붙은 변수의 값을 변경하려고 하면 컴파일 에러가 발생한다. 하지만 final 키워드가 객체의 상태를 변경하지 못하도록 막는 것은 아니다.

    final List<String> list = new ArrayList<>();
    list.add("a");

    위와 같이 List 객체가 final로 선언되었음에도 새로운 객체를 추가했을 때 문제가 발생하지 않는다. 이와 같은 문제가 발생할 수 있기 때문에 참조에 의해 값이 변경될 수 있는 점들을 유의하여 개발해야 한다.

    불변 클래스

    불변 객체를 생성하기 위해서는 다음과 같은 규칙에 따라서 클래스를 생성해야 한다.

    1. 클래스를 final로 선언한다.
    2. 모든 클래스 변수를 private와 final로 선언한다.
    3. 객체를 생성하기 위한 생성자 또는 정적 팩토리 메소드를 추가한다.
    4. 참조에 의해 변경 가능성이 있는 경우 방어적 복사를 이용하여 전달한다.
    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에서는 생성자를 선언하지 않으면 기본 생성자가 자동으로 생성되는데, 그러면 다른 클래스에서 해당 객체를 자유롭게 호출할 수 있다. 그렇기 때문에 내부 생성자를 만드는 대신 정적 팩토리 메소드를 통해 객체를 생성하도록 강요하는 것이 좋다.

    또한 배열이나 다른 객체 또는 컬렉션은 참조가 전달되어 수정가능성이 있다. 그렇기 때문에 참조를 통해 변경이 가능한 경우에는 방어적 복사를 통해 값을 반환해야 한다.

    불변 객체의 장단점

    장점

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

    멀티 쓰레드 환경에서 동기화 문제가 발생하는 이유는 공유 자원에 동시에 쓰기(Write) 때문이다. 하지만 만약 공유 자원이 불변의 자원이라면 항상 동일한 값을 반환하기 때문에 동기화를 고려하지 않아도 된다. 이는 안정성을 보장할 뿐만 아니라 동기화를 하지 않음으로써 성능상의 이점도 가져다준다.

     

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

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

    단점

    객체의 값이 할당될 때마다 새로운 객체가 필요하다. 따라서 메모리 누수와 성능저하를 발생시킬 수 있다.

     

    Mutable Object

    가변 객체는 불변 객체와 다르게 힙 영역에 생성된 객체를 변경할 수 있다. 우리가 자바에서 사용하는 대부분의 객체는 가변 객체이다. 가변객체는 멀티스레드 환경에서 사용하기 위해서 별도의 동기화 처리가 필요하다.


    Reference

    https://mangkyu.tistory.com/131

    https://cantcoding.tistory.com/41

    'Java' 카테고리의 다른 글

    [Java] Singleton Pattern  (0) 2022.01.03
    [Java] Serialization과 Deserialization  (0) 2022.01.03
    [Java] String, StringBuffer, StringBuilder  (0) 2022.01.03
    [Java] Identity와 Equality  (0) 2022.01.03
    [Java] Primitive type과 Reference type  (0) 2022.01.03

    댓글

Designed by Tistory.