Project/에러해결

Java UnsupportedOperationException 오류 해결

sesam 2025. 4. 24. 09:12
728x90

 

 

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 등과 관련 있기 때문에 반드시 수정 가능한 리스트가 필요함

📌 참고

 

 

 


  • toList
    Accumulates the elements of this stream into a List. The elements in the list will be in this stream's encounter order, if one exists. The returned List is unmodifiable; calls to any mutator method will always cause UnsupportedOperationException to be thrown. There are no guarantees on the implementation type or serializability of the returned List.The returned instance may be value-based. Callers should make no assumptions about the identity of the returned instances. Identity-sensitive operations on these instances (reference equality (==), identity hash code, and synchronization) are unreliable and should be avoided.
    This is a terminal operation.
    API Note:If more control over the returned object is required, use Collectors.toCollection(Supplier).Implementation Requirements:The implementation in this interface returns a List produced as if by the following:Implementation Note:Most instances of Stream will override this method and provide an implementation that is highly optimized compared to the implementation in this interface.Returns:a List containing the stream elementsSince:16
  • default List<T> 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())를 쓰는 게 좋은가?

  1. 결과 리스트에 값 추가/삭제할 가능성이 있을 때
  2. List<String> names = stream.collect(Collectors.toList()); names.add("newUser"); // 가능
  3. JPA 엔티티 필드에 리스트를 넣을 때 → JPA는 리스트를 내부적으로 수정하거나 proxy를 바꾸는 경우가 많음
  4. 리스트에 .sort(), .removeIf(), .clear() 등 메서드를 쓰려는 경우
  5. 자바 8 ~ 15 환경 → .toList() 자체가 없음 (Java 16부터 생김)

✅ 언제 toList()를 써도 괜찮은가?

  1. 리스트를 "절대로" 수정하지 않을 때
    예를 들면 컨트롤러에서 단순히 응답값으로 넘기는 DTO라면:
  2. return users.stream() .map(User::getUsername) .toList(); // 결과를 수정할 일이 없으면 안전함
  3. 불변성을 보장하고 싶은 경우 → 함수형 프로그래밍에서는 가급적 불변 데이터 구조를 유지하는 게 좋기 때문
  4. 성능이 중요한 경우
    toList()는 내부적으로 최적화된 구현이 적용될 수 있음
    (Collectors.toList()는 기본적으로 ArrayList 사용)

🧠 결론: 언제 뭘 써야 하냐?

상황 추천 메서드 이유

수정할 수도 있다면 collect(Collectors.toList()) 안전하고 가변
절대 수정 안 한다면 toList() 간결하고 의도가 명확함
자바 8~15 collect(Collectors.toList()) toList() 미지원
리팩터링 중 리스트 수정 여부 불확실 Collectors.toList() 방어적 코드 작성

💡 Tip

👉 "헷갈리면 collect(Collectors.toList()) 써라"가 실무에서는 가장 안전한 기본값이야.
불변 리스트가 필요한 건 아주 명확한 상황에서만 쓰면 됨.