Java 16에서 추가된 Stream.toList(). 기존 collect(Collectors.toList())보다 짧아서 무심코 일괄 치환했다가 UnsupportedOperationException을 만나는 경우가 있다.
결론부터
| 메서드 | 반환 List 변경 가능 여부 |
|---|---|
Stream.toList() (Java 16+) | 불가능 (immutable) |
Collectors.toList() | 가능 (mutable, 일반적으로 ArrayList) |
Collectors.toUnmodifiableList() | 불가능 (immutable) |
코드로 비교
| |
이름과 결과는 비슷하지만 동작이 다르다.
왜 다른가
Collectors.toList()의 명세에는 “반환 리스트의 type, mutability, serializability, thread-safety는 보장하지 않는다"라고 적혀 있다. 다만 실제 구현이 오랜 기간 ArrayList였고, 사람들이 그 동작에 의존해서 add를 호출해 왔다. 코드베이스 곳곳에 mutable 가정이 박혀 있는 상태다.
Stream.toList()는 새로 추가되면서 처음부터 immutable이라고 명시했다. 명세대로 변경 메서드는 던진다.
어떻게 갈아끼울지
- 결과를 그대로 읽기만 하면
Stream.toList()가 낫다. 짧고 의도(불변)도 명확하다. - 이후
add/remove/ 정렬 등 변경이 필요하면collect(Collectors.toList())또는new ArrayList<>(stream.toList()). - 명시적으로 불변을 보장하고 싶으면
Collectors.toUnmodifiableList()또는List.copyOf(...).
마무리
리팩토링하면서 IDE 일괄 변환으로 .collect(Collectors.toList())를 .toList()로 모두 바꿨다가, mutable에 의존한 코드가 한참 후 단위 테스트에서 깨지는 경우가 있다. 같은 이름이라도 명세가 다르면 결과도 다르다.