관리 메뉴

백엔드 엔지니어 이재혁

[Java] List.of() 메서드 본문

흥미로운 것들

[Java] List.of() 메서드

alex00728 2025. 6. 18. 01:25

`List.of()`는 매개변수가 0개인 것부터 10개인 것까지, 그리고 매개변수를 무제한으로 받는 버전이 존재한다.

처음엔 도대체 이게 무슨 짓인가... 하고 궁금해서 조금 깊이 뒤져봤다.

// 매개 변수가 없는 경우
static <E> List<E> of() {
    return (List<E>) ImmutableCollections.EMPTY_LIST;
}

static <E> List<E> of(E e1) {
    return new ImmutableCollections.List12<>(e1);
}

static <E> List<E> of(E e1, E e2) {
    return new ImmutableCollections.List12<>(e1, e2);
}

static <E> List<E> of(E e1, E e2, E e3) {
    return ImmutableCollections.listFromTrustedArray(e1, e2, e3);
}

// ... 생략 매개변수가 4개인 것부터 9개인 것까지도 추가로 존재한다.

static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) {
    return ImmutableCollections.listFromTrustedArray(e1, e2, e3, e4, e5,
                                                     e6, e7, e8, e9, e10);
}

// 그리고 매개변수 전체를 배열로 바꿔서 사용하는 List.of() 메서드
static <E> List<E> of(E... elements) {
    switch (elements.length) { // implicit null check of elements
        case 0:
            @SuppressWarnings("unchecked")
            var list = (List<E>) ImmutableCollections.EMPTY_LIST;
            return list;
        case 1:
            return new ImmutableCollections.List12<>(elements[0]);
        case 2:
            return new ImmutableCollections.List12<>(elements[0], elements[1]);
        default:
            return ImmutableCollections.listFromArray(elements);
    }
}

 

`List12`와 `ListN`까지 확인하고 나니 극한의 최적화를 위해 이렇게까지 메서드 오버로딩을 해놨다는 결론이 나왔다.

 

이후는 `List12`와 `ListN`을 확인하고 발견한 몇 가지 간단한 특징들을 소개해보겠다.

 

ImmutableCollections

모든 `List.of()` 메서드는 `ImmutableCollections`의 `List12` 객체나 `ListN` 객체를 return 한다.

두 객체는 모두 `AbstractImmutableList`를 상속받았는데, 그 이름에 맞게 `Collection` 인터페이스의 입력/수정/삭제와 같이 컬렉션에 변화(mutation)를 주는 메서드들을 호출하면 `UnsupportedOperationException`이 던져지도록 설계되어 있다.

// all mutating methods throw UnsupportedOperationException
@Override public void    add(int index, E element) { throw uoe(); }

// uoe() 메서드 정의
static UnsupportedOperationException uoe() { return new UnsupportedOperationException(); }

 

 

`List.of()`가 return하는 `List12<E>`와 `ListN<E>`는 약간의 특징이 있다.

 

null 체크

`List.of()` 메서드로 return 받는 Immutable List는 List를 넘겨주기 전에 배열에 null 값이 있는지 먼저 확인해준다. 만약 null이 있다면 List를 반환하지 않고, NPE (`NullPointerException`)을 던진다.

 

`List.of()` 메서드는 `List12`와 `ListN` 둘 중 하나를 return 한다.

  • `List12`는 생성자에서 `null` 체크를 해준다.
  • `ListN`는 `ImmutableCollections.listFromTrustedArray()` 또는 `ImmutableCollections.listFromArray()`를 거쳐 `ListN`을 생성하며, 그 과정에서 `null` 체크를 해준다.

이런 과정을 통해 `List.of()`에서 반환되는 List는 null을 포함하지 않을 것이라고 가정하고 프로그램을 개발할 수 있다. 프로그래밍할 때 가장 무서운 NPE를 사전에 확인할 수 있는 좋은 메서드 ㅎㅎ

 

클래스 구현 단순화

`ArrayList<E>` 클래스는 `size` 필드를 유지한다. 이를 기반으로 List의 size 정보를 제공한다.

private int size;
public int size() {
    return size;
}

 

반면, `List12<E>` 클래스에는 `size` 필드가 없다. size 메서드에서 간단한 계산을 통해 제공한다.

@Override
public int size() {
    return e1 != EMPTY ? 2 : 1; // final EMPTY = null
}

 

`ListN<E>` 클래스에도 `size` 필드가 없다.

`ListN` 생성 시점에 `E[] element` 배열의 길이를 필요한 만큼만 딱 만들고 그 배열의 길이를 size()의 return으로 사용하면 된다.

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

 

정리: Immutable 하다는 점을 이용해 메모리 사용과 로직을 최대한 단순화시켰다. (`List12<E>`와 `ListN<E>`)

 

스레드-안전

수정 가능성이 없다는 것은 스레드 안전하다는 것이기도 하다. `List.of()`로 생성한 객체는 멀티스레드 환경에서 편하게 쓰자.

 

 

결론

`List.of()`로 받는 List는 Null 체크를 해주며, 스레드 안전하다. 약간의 최적화는 덤.

 

참고로 `Set.of()`, `Map.of()`도 불변 객체를 반환한다.