Java

collect(Collectors.toList()) vs Stream.toList()

void_melody 2023. 11. 27. 11:13

평소 스트림을 리스트로 변환할 때 단순히 Stream.toList()를 활용해왔다.

하지만 어제 프로그래머스에서 코테 문제를 풀다가 Stream.toList()를 지원하지 않고 Collectors.toList()만 지원되는 것을 보고, 이 둘의 차이점을 조금 더 명확히 알 필요가 생겨 정리하게 되었습니다.

(우선 결론부터 말하자면.. stream의 toList는 자바 16이상 버전부터 지원하기에, 프로그래머스는 해당 버전이 아니어서 지원을 안한걸로)

 

 

우선 Collectors.toList()의 자바 공식 문서 설명을 보면 다음과 같습니다.

Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned;

 

여기서 보면, mutability 즉 변경 가능성에 대해 보장하지 않는다 한다.

코드를 보며 확인해보자.

    @DisplayName("Collectors.toList() 수정 테스트")
    @Test
    void collectorsToListTest(){
        //Given
        List<String> list = Stream.of("aaa", "bbb")
                .collect(Collectors.toList());
        //When
        list.add("ccc");

        //Then
        assertThat(list).hasSize(3);
     }


     @DisplayName("Stream.toList() 수정 테스트")
     @Test
     void streamToListTest(){
         //Given
         List<String> list = Stream.of("aaa", "bbb").toList();
         //When //Then
         assertThatThrownBy(() -> list.add("ccc"))
                 .isInstanceOf(UnsupportedOperationException.class);
      }

 

둘의 가장 큰 차이는, 반환하는 타입이다.

Collectors.toList()는 ArrayList를 반환한다.

그렇기에 수정을 해도 문제가 없는 것이다.

반면에 Stream.toList()는 Collectors.UnmodifiableList를 반환한다.

그렇기에 해당 메서드를 사용하면 수정하거나 추가하는 등의 작업을 할 수 없는 것이다.

 

우리가 코딩 테스트 문제들을 풀다보면, 보통 리스트를 반환받고 해당 리스트에 요소를 추가하는 경우가 생긴다.

List<Integer> keySet = indexMap.keySet().stream()
					.sorted()
					.collect(Collectors.toList());
keySet.add(0);

 

그런데 이걸 Collectors.toList()가 아니라, Stream.toList() 사용했다면, UnmodifiableList를 반환했기 때문에, 
예외가 발생할 것이다.

static class UnmodifiableList<E> extends UnmodifiableCollection<E> implements List<E> {
    ...
    public E get(int index) {return list.get(index);}
    public E set(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }
    public int indexOf(Object o)            {return list.indexOf(o);}
    public int lastIndexOf(Object o)        {return list.lastIndexOf(o);}
    public boolean addAll(int index, Collection<? extends E> c) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void sort(Comparator<? super E> c) {
        throw new UnsupportedOperationException();
    }
    ...
}

 

set, add, remove 등의 수정 작업에는 다 UnsupportedOperationException을 던지는 걸 확인할 수 있다.

 

그렇다면 드는 생각은, 그러면 Collectors.toUnmodifiableList()를 사용하면 Stream.toList()와 동일한 기능을 할 것인가? 라는 의문이다.

 

하지만 약간 다르다.
collect(Collectors.toUnmodifiableList())는 요소에 null을 허용하지 않는다.

하지만 Stream.toList()는 null 요소를 허용한다.

 

우선 저의 개인적인 생각은.. 수정하지 않고 반환할 리스트라면 Stream.toList()를 사용할 것 같지만..
코테처럼 해당 리스트에 조작을 가하는 작업이 빈번하게 일어나는 경우라면 Collectors.toList를 적극 활용할 것 같습니다.