SOFTMOA TECHNOLOGY

Java List.add에서 UnsupportedOperationException이 날 때 Arrays.asList 확인 방법

소프트모아가 정리한 기술 기록입니다.

Contents
작성일 2026. 11. 14.

Java에서 List.add()가 UnsupportedOperationException으로 실패하면 리스트를 만든 코드를 먼저 확인하세요. Arrays.asList()는 크기가 고정된 리스트라 원소를 추가하지 못합니다. 추가·삭제가 필요하면 new ArrayList<>(기존리스트)로 작업용 리스트를 만드세요. 다만 기존 자리의 값을 바꾸는 set()은 허용하며 원본 배열까지 바꿉니다. 추가 실패와 원본 변경은 서로 다른 문제입니다.

List 선언만으로는 수정 가능 여부를 알 수 없습니다

List<String>이라는 타입은 사용 가능한 메서드를 알려 주지만 모든 구현이 add()를 지원한다는 보장은 아닙니다. 예외가 난 줄의 실제 변수가 어디서 왔는지 확인해야 합니다. 같은 메서드 안의 다른 리스트를 ArrayList로 바꿔도 실패한 객체가 그대로면 해결되지 않습니다. List API는 add()를 선택적 연산으로 정의합니다.

Arrays.asList()의 반환값은 원본 배열을 함께 쓰는 리스트입니다. 크기 변경과 값 교체는 구분하세요. Arrays.asList() 명세에서도 크기 변경을 제한하고 배열과 리스트의 변경이 서로 보인다고 설명합니다. 운영 데이터에 add()나 set()을 시험하지 말고 아래처럼 별도 입력으로 재현하세요.

추가는 실패하지만 원본 값은 바뀌는 재현

아래는 문자열 두 개로 만든 테스트입니다. 상태 이름은 예시일 뿐 실제 업무 처리를 실행하지 않습니다. ListProbe.java로 저장한 뒤 다음 명령으로 실행합니다. 로컬 Temurin 21.0.10과 25.0.4.1에서 같은 결과를 확인했습니다. DB·외부 API·파일 내용은 변경하지 않습니다.

java -version
java ListProbe.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ListProbe {
    static void probe(String label, Runnable action) {
        try {
            action.run();
            System.out.println(label + ": OK");
        } catch (UnsupportedOperationException e) {
            System.out.println(label + ": UnsupportedOperationException");
        }
    }
    public static void main(String[] args) {
        String[] source = {"PENDING", "HOLD"};
        List<String> view = Arrays.asList(source);
        probe("view.add", () -> view.add("NEW"));
        probe("view.set", () -> view.set(0, "READY"));
        System.out.println("source=" + Arrays.toString(source));

        List<String> work = new ArrayList<>(view);
        probe("work.add", () -> work.add("NEW"));
        work.set(0, "DONE");
        System.out.println("work=" + work);
        System.out.println("view=" + view);
        System.out.println("source=" + Arrays.toString(source));

        List<String> locked = List.of("PENDING", "HOLD");
        probe("locked.set", () -> locked.set(0, "READY"));
        System.out.println("locked=" + locked);
    }
}

실행 결과는 다음과 같습니다. probe()는 예외 이후의 상태까지 비교하려고 UnsupportedOperationException만 잡습니다. 운영 코드에서 오류를 무시하라는 뜻은 아닙니다.

view.add: UnsupportedOperationException
view.set: OK
source=[READY, HOLD]
work.add: OK
work=[DONE, HOLD, NEW]
view=[READY, HOLD]
source=[READY, HOLD]
locked.set: UnsupportedOperationException
locked=[PENDING, HOLD]

새 리스트가 고친 것과 그대로 남긴 것

view.add는 실패해서 NEW를 넣지 못했지만 view.set은 성공해서 source의 첫 값이 PENDING에서 READY로 바뀌었습니다. 그 뒤 work에 NEW를 추가하고 첫 값을 DONE으로 교체해도 view와 source는 [READY, HOLD]로 남습니다. 원소 수는 work만 3개이고 나머지는 2개입니다.

복사 시점에는 이미 READY였습니다. new ArrayList<>(view)는 원본을 PENDING으로 복구하지 않습니다. 원본을 보존하려면 처음 값을 바꾸기 전에 작업용 리스트를 만들고 그 리스트만 수정하세요. ArrayList 생성자와 연산 명세를 기준으로, 이 예제는 리스트의 추가와 자리 교체를 분리한 것입니다.

locked는 List.of()로 만든 리스트라 set()도 실패합니다. Arrays.asList()와 같은 고정 크기라는 이유로 둘을 같은 성질로 취급하면 안 됩니다. List.of()의 수정 제한은 추가·삭제뿐 아니라 원소 교체에도 적용됩니다.

수정 범위를 정하고 생성 코드를 고칩니다

  • 배열과 값을 공유하되 크기는 유지하려면 Arrays.asList()의 동작이 맞는지 확인합니다.
  • 추가·삭제하는 별도 작업 목록이 필요하면 변경 전에 new ArrayList<>(기존리스트)를 만듭니다.
  • 호출자가 원소를 추가·삭제·교체하지 못하게 하려면 List.of() 같은 수정 불가 리스트의 계약을 사용합니다. List.of()는 null 원소도 허용하지 않습니다.

여기서 복사한 것은 리스트의 원소 참조입니다. 문자열 대신 수정 가능한 객체를 넣으면 두 리스트가 같은 객체를 가리킬 수 있습니다. 객체 내부 필드까지 독립시킨 복제나 스레드 안전성을 보장하지 않습니다. 수정 불가 리스트도 그 안의 객체 자체를 불변으로 만들지는 않습니다.

수정 후에는 예외 유무와 함께 원본·작업용 리스트의 값을 각각 비교하세요. 예외가 사라져도 원본이 보존됐는지는 별도로 확인하세요. 업무 시스템 개발 안내는 softmoa.com에서 확인하세요.

소프트모아는 해당 시스템을 구축합니다. 문의하기

Share this

Java

Have a questions?

견적 및 기술문의

mobile : 010-7931-4813

Contact Form