본문 바로가기
Old Posts/Java

[Java] Unmodifiable Collection vs Immutable 차이점

by A6K 2021. 7. 26.

카프카 코드리뷰를 하다가 'Unmodifiable Collection'이라는 것을 보게 되었다. 카프카 소스코드는 다음과 같았다.

private List<ConsumerRecord<K, V>> drainRecords(int n) {
    if (isDrained() || position >= records.size()) {
        drain();
        return Collections.emptyList();

     }

    // using a sublist avoids a potentially expensive list copy (depending on the size of the records
    // and the maximum we can return from poll). The cost is that we cannot mutate the returned sublist.
    int limit = Math.min(records.size(), position + n);
    List<ConsumerRecord<K, V>> res = Collections.unmodifiableList(records.subList(position, limit));

    position = limit;
    if (position < records.size())
        fetchOffset = records.get(position).offset();

    return res;
}

 'Collections.unmodifiableList(records.subList(position, limit))' 코드에서 기존에 존재하는 리스트를 인자로 받아 새로운 리스트로 리턴해준다. 

코드에 쓰여 있는 주석을 읽어보면, 불필요한 리스트 복사 동작을 피하기 위함이라고 적혀있다. 'Collections.unmodifiableList()' 메소드를 열어보면 다음과 같다.

/**
* Returns an <a href="Collection.html#unmodview">unmodifiable view</a> of the
* specified list. Query operations on the returned list "read through" to the
* specified list, and attempts to modify the returned list, whether
* direct or via its iterator, result in an
* {@code UnsupportedOperationException}.<p>
*
* The returned list will be serializable if the specified list
* is serializable. Similarly, the returned list will implement
* {@link RandomAccess} if the specified list does.
*
* @param <T> the class of the objects in the list
* @param list the list for which an unmodifiable view is to be returned.
* @return an unmodifiable view of the specified list.
*/
public static <T> List<T> unmodifiableList(List<? extends T> list) {
    return (list instanceof RandomAccess ?
        new UnmodifiableRandomAccessList<>(list) :
        new UnmodifiableList<>(list));
}

주석을 요약해보면 unmodifiableList() 메소드에서 리턴되는 리스트 객체는 'Read-Only' 용도로만 사용할 수 있으며 수정하려는 메소드를 호출하면 UnsupportedOperationException이 발생한다. 실제로 리턴되는 객체에 set(), add(), addAll() 메소드를 호출하면 예외가 발생한다.

'Unmodifiable(수정 할 수 없다)'라는 특징은 마치 Immutable을 떠올리게 한다. unmodifiable과 immutable의 차이를 알아보자.


Unmodifiable

Unmodifiable은 직역하면 '수정할수 없는'이라는 의미다. Collection.unmodifiableList() 같음 메소드에서 리턴되는 객체는 수정을 가할 수 없다. set(), add(), addAll() 등의 메소드를 호출하면 UnsupportedOperationException이 발생한다.

하지만 절대 수정하지 못하는 것은 아니다. 원본 리스트가 수정되는 것은 막을 수 없으며, Collection.unmodifiableList() 메소드로 리턴받은 변수 이외에 다른 경로를 통해 리스트를 수정할 수는 있다.

다음 코드를 살펴보자

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Test {

    public static void main(String []args) {

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

        List<String> unmodifiableList = Collections.unmodifiableList(list);

        try {
            unmodifiableList.add("d");
            System.out.println("cannot be reached here");
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify unmodifiable list");
        }

        list.add("d");

        System.out.println(unmodifiableList.get(3));
    }
}

list에 데이터를 넣고 Collections.unmodifiableList()를 통해 unmidifiable 리스트 객체를 얻어왔다. 이후 원본 리스트인 list를 통해 리스트를 수정하면 unmodifiable 리스트 변수를 통해 출력해도 list로 추가한 데이터를 확인할 수 있다.

이 코드를 수행하면 다음과 같은 결과를 얻을 수 있다.

Cannot modify unmodifiable list
d

이처럼 unmodifiable 레퍼런스로 수정은 못하지만 원본 자체에 대한 수정을 막을 수는 없다.


Immutable

리스트가 Immutable 하다고 하면 어떤 레퍼런스를 이용해서라도 수정할 수 없어야 한다. 위에서 본 unmodifiableList는 원본 리스트로의 레퍼런스는 수정을 할 수 있기 때문에 Immutable을 만족하지 않는다.

기존에 존재하는 컬렉션을 Immutable로 만들기 위해서는 기존 컬렉션의 데이터를 새로운 컬렉션으로 복사한 다음, 새로운 컬렉션으로의 수정(modify) 접근을 제한하는게 일반적이다.

예를들어

List<String> immutableList = Collections.unmodifiableList(new ArrayList<String>(list));

이런 코드는 기존의 컬렉션인 list의 내용을 복사해서 unmodifiableList를 만들기 때문에 여기서 리턴되는 List는 immutable이다.

댓글