영호

[JAVA] Collections.emptyList() vs List.of() 본문

Language/JAVA

[JAVA] Collections.emptyList() vs List.of()

0h0 2023. 3. 18. 16:06

개요

우아한테크코스 체스미션 진행 중 빈 리스트를 반환해야 하는 일이 있었는데, 그동안 빈 리스트를 반환할 때 무심코 사용하던 코드들에 대해 어떤 차이가 있는지 궁금해서 찾아본 과정에 대해 작성해보겠습니다:)

 

Collections.emptyList()

처음에는 단순히 Collections에서 정적 팩토리 메서드를 이용해 빈 리스트를 생성해 반환해주나? 라는 생각으로 접근했습니다. Collections.emptyList()내부 구현을 봤더니 예상하지 못했던 코드가 있었습니다.

 

EmptyList라는 클래스가 존재한다.

EmptyList라는 클래스가 별도로 존재했고 이를 List<T>타입으로 캐스팅해 반환해주고 있었습니다.

EmptyList의 Collections의 메서드들에 대해 빈 리스트에 맞게 정의되어 있습니다. get을 하려하면 IndexOutOfBoundsException예외 발생, size()는 return 0; 등이 있습니다.

아래 캡쳐본은 static EmptyList클래스의 구현 코드 중 일부입니다.

Immutable하다.

공식문서를 보면 아래와 같이 나와있습니다.

즉, 수정이 불가능한 리스트가 반환됩니다.

List<Object>가 반환된다.

List<Object> emptyList = Collections.emptyList();

위 코드와 같이 Collections.emptyList()는 List<Object>가 반환됩니다.

 

List<Object> emptyList = Collections.emptyList();
System.out.println(emptyList.getClass());
emptyList.add("1");

emptyList는 Immutable하기 때문에 add메서드를 호출하면 UnsupportedOperationException이 발생합니다.

 

EmptyList외 다양한 Empty자료구조

List외에도 Map, Set등 다양한 자료 구조에 대해 EmptyList와 동일하게 구현되어 있습니다.

 


List.of()

List.of()를 통해서도 리스트를 생성할 수 있습니다. 다만 List.of()는 ImmutableCollections의 리스트가 반환되기 때문에 이 역시 불변합니다.

 

List인터페이스의 of메서드

static <E> List<E> of() {
    return ImmutableCollections.emptyList();
}

ImmutableCollections의 emptyList메서드

static <E> List<E> emptyList() {
    return (List<E>) ListN.EMPTY_LIST;
}

세부 구현 코드

static final class ListN<E> extends AbstractImmutableList<E>
        implements Serializable {

    // EMPTY_LIST may be initialized from the CDS archive.
    static @Stable List<?> EMPTY_LIST;

    static {
        VM.initializeFromArchive(ListN.class);
        if (EMPTY_LIST == null) {
            EMPTY_LIST = new ListN<>();
        }
    }

    @Stable
    private final E[] elements;

    @SafeVarargs
    ListN(E... input) {
        // copy and check manually to avoid TOCTOU
        @SuppressWarnings("unchecked")
        E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input
        for (int i = 0; i < input.length; i++) {
            tmp[i] = Objects.requireNonNull(input[i]);
        }
        elements = tmp;
    }

    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    @Override
    public int size() {
        return elements.length;
    }

    @Override
    public E get(int index) {
        return elements[index];
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        throw new InvalidObjectException("not serial proxy");
    }

    private Object writeReplace() {
        return new CollSer(CollSer.IMM_LIST, elements);
    }
}

List.of(), Collections.emptyList() 공통점

둘 다 불변이기 때문에 add, remove등의 작업을 수행하려고 하면 예외가 발생합니다. 

 

List.of(), Collections.emptyList() 차이점

get()

List.of()의 경우 생성 시에 리스트에 원소를 설정할 수 있기 때문에 get메서드 호출 시, 예외가 발생하지 않습니다.

Collections.emptyList()의 경우 내부에 원소가 있을 수 없기 때문에 get()호출 시 UnsupportedOperationException 발생합니다.

size()

List.of()는 위와 마찬가지로 생성 시 원소 설정이 가능하기 때문에 아래 코드와 같이 동작합니다.

public int size() {
    return elements.length;
}

Collections.emptyList()는 비어있기 때문에 무조건 0을 반환합니다.

public int size() {return 0;}

 

이 외에도 isEmpty(), contains() 등등의 메서드에서 차이가 있습니다.


결론

List.of()와 Collections.emptyList()를 아래와 같이 사용할 것 같습니다.

  • 만약 정말 아무것도 없는 불변 리스트를 반환하고 싶으면 Collections.emptyList()사용
  • 불변이지만 내부 원소를 선언하고 싶으면 List.of()사용
Comments