Java UnsupportedOperationException 오류 해결
Java UnsupportedOperationException 오류 해결
– .toList()가 문제
✨ 문제 상황
Spring Boot 프로젝트에서 사용자 목록을 처리하던 중, 다음과 같은 예외가 발생했다.
java.lang.UnsupportedOperationException
코드는 대략 다음과 같은 구조였다:
List<String> usernames = List.of("alice", "bob", "charlie");
usernames.add("david"); // ❌ 여기서 예외 발생
List.of(...)는 불변 리스트(immutable list)를 반환하기 때문에 .add()와 같은 수정 메서드는 허용되지 않는다.
비슷한 문제는 Stream API의 .toList() 사용 시에도 발생할 수 있다.
List<String> usernames = users.stream()
.map(User::getUsername)
.toList(); // Java 16+ ❗불변 리스트
usernames.add("newUser"); // ❌ 예외 발생
🔍 원인 분석
Java 16부터 제공되는 Stream.toList()는 Collections.unmodifiableList(...)를 사용해 내부적으로 불변 리스트를 생성한다.
따라서 .add(), .remove() 등 수정 작업을 시도하면 UnsupportedOperationException이 발생하게 된다.
✅ 해결 방법
.toList() 대신 Collectors.toList()를 사용하면 수정 가능한 ArrayList가 생성된다:
List<String> usernames = users.stream()
.map(User::getUsername)
.collect(Collectors.toList()); // ✅ 수정 가능
usernames.add("newUser"); // ✅ 정상 작동
🧠 실무 팁
- 불변 리스트가 필요한 경우가 아니라면, 무조건 collect(Collectors.toList()) 사용 권장
- 특히 DTO에서 entity로 변환하거나, 컬렉션을 반환하고 나서 수정할 가능성이 있는 경우 반드시 가변 리스트로 반환할 것
- JPA에서는 엔티티의 컬렉션 필드가 lazy loading, dirty checking 등과 관련 있기 때문에 반드시 수정 가능한 리스트가 필요함
📌 참고
- Java 공식 문서 – Stream.toList()
- 관련 토론: Why does Stream.toList return unmodifiable list? – Stack Overflow
- Java 공식 문서 – Stream.toList()
|
좋아, 너가 제공한 공식 Java 문서 내용에서 UnsupportedOperationException 오류가 발생하는 직접적인 이유는 명확하게 언급돼 있어.
🔍 오류 원인이 명시된 문서 부분
📌 문장 1 — 불변 리스트(unmodifiable list)임을 명시
"The returned List is unmodifiable; calls to any mutator method will always cause UnsupportedOperationException to be thrown."
"반환된 List는 변경할 수 없으며(mutator method), 어떤 변경 메서드를 호출하면 UnsupportedOperationException이 항상 발생한다."
✅ 이 문장이 네가 겪은 오류를 정확히 설명하는 부분임.
toList()로 만든 리스트에 .add(), .remove() 등을 쓰면 예외가 발생하게 되는 이유임.
📌 문장 2 — 내부 구현 방식
"The implementation in this interface returns a List produced as if by the following:"
Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())))
✅ 이 코드처럼 ArrayList를 만들고 Collections.unmodifiableList(...)로 **수정 불가 래퍼(wrapper)**로 감싸기 때문에,
결과적으로 리스트는 **읽기 전용(read-only)**이 됨.
📌 문장 3 — 대안 제시
"If more control over the returned object is required, use Collectors.toCollection(Supplier)."
✅ 리스트를 수정할 필요가 있다면, Collectors.toList() 또는 Collectors.toCollection(ArrayList::new)을 쓰라는 말.
✨ 정리하자면
문서 내용 설명 오류와의 연관
"The returned List is unmodifiable..." | 리스트가 불변이다 | .add() → UnsupportedOperationException 발생 |
"Collections.unmodifiableList(...)" | 내부 구현이 불변으로 감쌈 | 리스트가 수정 불가능하게 설정되어 있음 |
"Use Collectors.toCollection(...) if needed" | 가변 리스트 필요 시 대안 제시 | 실무에선 이걸 써야 예외 안 남 |
그래서 결론은 👉 문서에 너가 겪은 오류의 원인과 해결책이 아주 명확하게 들어 있음.
해당 문구를 블로그에 인용하면 신뢰도와 전문성이 확 올라갈 거야.
필요하면 문서 인용 블록 + 실무 해석 같이 붙여서 정리해줄게.
Q) 그렇다면 항상 collect(Collectors.toList()) 을 사용하는 게 좋은거 아님?
A) 정답부터 말하면 👉 “대부분의 경우는 collect(Collectors.toList())를 쓰는 게 더 안전하고 실용적이다”.
하지만 항상 그렇다고는 말할 수 없어. 상황에 따라 선택이 달라져야 함.
✅ 언제 collect(Collectors.toList())를 쓰는 게 좋은가?
- 결과 리스트에 값 추가/삭제할 가능성이 있을 때
- List<String> names = stream.collect(Collectors.toList()); names.add("newUser"); // 가능
- JPA 엔티티 필드에 리스트를 넣을 때 → JPA는 리스트를 내부적으로 수정하거나 proxy를 바꾸는 경우가 많음
- 리스트에 .sort(), .removeIf(), .clear() 등 메서드를 쓰려는 경우
- 자바 8 ~ 15 환경 → .toList() 자체가 없음 (Java 16부터 생김)
✅ 언제 toList()를 써도 괜찮은가?
- 리스트를 "절대로" 수정하지 않을 때
예를 들면 컨트롤러에서 단순히 응답값으로 넘기는 DTO라면: - return users.stream() .map(User::getUsername) .toList(); // 결과를 수정할 일이 없으면 안전함
- 불변성을 보장하고 싶은 경우 → 함수형 프로그래밍에서는 가급적 불변 데이터 구조를 유지하는 게 좋기 때문
- 성능이 중요한 경우
toList()는 내부적으로 최적화된 구현이 적용될 수 있음
(Collectors.toList()는 기본적으로 ArrayList 사용)
🧠 결론: 언제 뭘 써야 하냐?
상황 추천 메서드 이유
수정할 수도 있다면 | collect(Collectors.toList()) | 안전하고 가변 |
절대 수정 안 한다면 | toList() | 간결하고 의도가 명확함 |
자바 8~15 | collect(Collectors.toList()) | toList() 미지원 |
리팩터링 중 리스트 수정 여부 불확실 | Collectors.toList() | 방어적 코드 작성 |
💡 Tip
👉 "헷갈리면 collect(Collectors.toList()) 써라"가 실무에서는 가장 안전한 기본값이야.
불변 리스트가 필요한 건 아주 명확한 상황에서만 쓰면 됨.