본문 바로가기
카테고리 없음

[Java] Unmodifiable Collection vs Immutable 차이점

by 왕 달팽이 2019. 5. 29.
반응형

카프카 컨슈머 코드리뷰를 하다가 '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))'라는 부분이다. 기존의 List를 인자로 받아서 새로운 리스트를 리턴받는 메소드다.

 

코드에 쓰여 있는 주석을 읽어보면, 불필요한 리스트 복사 동작을 피하기 위함이라고 적혀있다. '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' 용도로만 사용할 수 있으며, 수정하려는 메소드(가령 set(), add(), addAll() 등)를 호출하면 UnsupportedOperationException 이 발생한다는 것이다. 

 

'Unmodifiable(수정 할 수 없다)'라는 특징은 마치 Immutable을 떠올리게 한다. unmodifiable과 immutable의 차이는 뭘까?

 

Unmodifiable

위 예제에서 본 것처럼 Collection.unmodifiableList() 같은 메소드에서 리턴되는 레퍼런스는 원본 컬렉션으로의 수정 메소드를 호출 할 수 없다. (만약 호출한다면 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));
	}
}

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

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이다. 

반응형

댓글