빠른 비교
List<String> roles = List.of("ADMIN", "EDITOR", "VIEWER");
Set<Integer> levels = Set.of(1, 2, 3);갈리는 기준
어떤 컬렉션 생성 방식을 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 수정 불가능한 작은 컬렉션 선언 | List.of, Set.of, Map.of |
| 기존 컬렉션을 읽기 전용으로 노출 | Collections.unmodifiableList(...) |
| 이후 수정이 필요한 컬렉션 | new ArrayList<>(...) 등 가변 컬렉션 |
| 원소까지 불변이어야 함 | 컬렉션 불변성과 원소 불변성을 따로 설계 |
수정 가능 vs 수정 불가능 컬렉션
Java 컬렉션을 배울 때는 보통 ArrayList 같은 수정 가능한 컬렉션부터 익히지만, 실무에서는 "이 컬렉션이 바뀌어도 되는가"가 더 중요한 질문이 되는 경우가 많습니다. 코드 의도를 분명히 드러내고, 여러 곳에서 공유될 때 예상치 못한 변경을 줄이려면 처음부터 불변 컬렉션을 선택하는 편이 좋습니다.
List.of, Set.of, Map.of 특성
JDK 9부터 추가된 편의 생성 메서드로, 수정 불가능한 컬렉션을 간결하게 만들 수 있습니다. null 원소를 허용하지 않으며, add·remove·set 같은 변경 연산 호출 시 UnsupportedOperationException이 발생합니다.
List<String> roles = List.of("ADMIN", "EDITOR");
roles.add("VIEWER"); // UnsupportedOperationException!
roles.set(0, "USER"); // UnsupportedOperationException!
// 이후 수정이 필요하다면 수정 가능한 복사본으로
List<String> mutable = new ArrayList<>(roles);
mutable.add("VIEWER"); // OKCollections.unmodifiableList vs List.of
Collections.unmodifiableList(original)은 기존 리스트의 읽기 전용 뷰를 만듭니다. 뷰를 통한 수정은 막히지만 원본 리스트를 수정하면 뷰에도 반영됩니다. List.of는 처음부터 완전히 독립된 불변 리스트를 만들므로 원본 참조가 없습니다.
List<String> mutable = new ArrayList<>(List.of("a", "b", "c"));
List<String> view = Collections.unmodifiableList(mutable);
// view.add("d"); // UnsupportedOperationException — 뷰 수정 불가
mutable.add("d"); // ✅ 원본 수정
System.out.println(view); // [a, b, c, d] — 뷰에도 반영됨!
// List.of는 독립 복사본 — 원본 변경에 영향 없음
List<String> snapshot = List.of("a", "b", "c");List.of vs unmodifiableList vs copyOf
새 고정 데이터를 바로 만들면 List.of, 기존 컬렉션의 읽기 전용 뷰를 노출하면 unmodifiableList, 원본과 분리된 스냅샷이 필요하면 List.copyOf가 더 직접적입니다.
List<String> source = new ArrayList<>(List.of("a", "b"));
List<String> view = Collections.unmodifiableList(source);
List<String> snapshot = List.copyOf(source);
source.add("c");
System.out.println(view); // [a, b, c]
System.out.println(snapshot); // [a, b]컬렉션 불변성과 원소 불변성의 차이
"컬렉션이 수정 불가능하다"와 "그 안의 원소까지 완전히 불변이다"는 다른 문제입니다. 원소 객체가 가변이면 컬렉션이 수정 불가능해도 내부 상태 변화는 여전히 일어날 수 있습니다.
List<User> users = List.of(new User("Kim"), new User("Lee"));
// users.add(...) — 불가 (UnsupportedOperationException)
users.get(0).setName("Park"); // 원소 자체는 변경 가능!선택 기준
| 상황 | 적합한 선택 |
|---|---|
| 초기 데이터가 고정, 변경 없음 | List.of(...) / Set.of(...) |
| 이후 추가·제거 필요 | new ArrayList<>(...) |
| 수정 불가 맵 | Map.of(k1, v1, k2, v2) |
| 불변 컬렉션에서 수정 가능 복사본 | new ArrayList<>(immutableList) |
| 기존 리스트를 읽기 전용으로만 노출 | Collections.unmodifiableList(list) |
| 원본 변경에 영향 없는 완전 독립 스냅샷 | List.of(...) 또는 List.copyOf(list) |
주의할 점
List.of로 만든 컬렉션에 변경 연산을 호출하면 런타임 예외가 발생합니다.
// ❌ List.of 결과를 수정 가능한 것처럼 사용
List<String> list = List.of("a", "b", "c");
list.add("d"); // UnsupportedOperationException
list.remove("a"); // UnsupportedOperationException
// ✅ 이후 수정이 필요하면 처음부터 수정 가능한 컬렉션 사용
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
list.add("d"); // OKSet.of(...)와 Map.of(...)는 중복 key나 중복 원소를 허용하지 않습니다.
// ❌ 중복 원소
Set<String> roles = Set.of("ADMIN", "ADMIN"); // IllegalArgumentException
// ❌ 중복 key
Map<String, Integer> scores = Map.of("kim", 1, "kim", 2); // IllegalArgumentException참고 링크
2 sources