1. copyOf 메서드: List.copyOf, Set.copyOf, Map.copyOf
상황을 가정해 봅시다: 어떤 메서드가 리스트, 집합 또는 맵을 반환해야 하지만, 호출 측 코드가 그 컬렉션의 내용을 어떤 방식으로든 변경하지 못하게 하는 것이 중요합니다. 예를 들어 “지원되는 통화 목록”이나 “사용자 역할 집합” 같은 경우입니다. 우발적인 삽입/삭제 하나가 비즈니스 로직을 깨뜨릴 수 있습니다.
Java 10 이전에는 수동으로 컬렉션의 복사본을 반환하거나 Collections.unmodifiableList(list) 같은 래퍼를 사용해야 했습니다. 래퍼는 자기 자신을 통한 변경만 막을 뿐, 원본 컬렉션의 수정까지는 막지 못합니다.
이제는 더 신뢰할 수 있는 방법 — copyOf 메서드가 있습니다.
무엇인가요?
메서드 List.copyOf(Collection), Set.copyOf(Collection), Map.copyOf(Map) (Java 10)은 전달된 컬렉션이나 맵의 진짜 불변 복사본을 생성합니다.
- 복사본은 원본 컬렉션과 연결되어 있지 않습니다: 원본의 변경은 copyOf 결과에 영향을 주지 않습니다.
- 복사본은 변경할 수 없습니다: 어떤 수정도 UnsupportedOperationException을 발생시킵니다.
불변 복사본 생성 예시
import java.util.*;
public class CopyOfDemo {
public static void main(String[] args) {
List<String> modifiable = new ArrayList<>();
modifiable.add("Java");
modifiable.add("Python");
// 불변 복사본 생성
List<String> immutable = List.copyOf(modifiable);
// 복사본을 변경해 보자
try {
immutable.add("C++"); // UnsupportedOperationException을 던짐
} catch (UnsupportedOperationException e) {
System.out.println("불변 컬렉션에는 요소를 추가할 수 없습니다!");
}
// 원본 리스트를 변경
modifiable.add("Kotlin");
// 복사본은 변하지 않음!
System.out.println("원본 리스트: " + modifiable);
System.out.println("불변 복사본: " + immutable);
}
}
프로그램 출력:
불변 컬렉션에는 요소를 추가할 수 없습니다!
원본 리스트: [Java, Python, Kotlin]
불변 복사본: [Java, Python]
각 메서드 요약
- List.copyOf(Collection) — 원본 컬렉션의 요소로 불변 리스트를 반환합니다.
- Set.copyOf(Collection) — 컬렉션의 요소로 불변 집합을 반환합니다; 중복은 제거됩니다.
- Map.copyOf(Map) — 원본 맵의 엔트리로 불변 맵을 반환합니다.
Set과 Map 예시
import java.util.*;
public class CopyOfSetMapDemo {
public static void main(String[] args) {
Set<String> modifiableSet = new HashSet<>(Set.of("A", "B", "C"));
Set<String> immutableSet = Set.copyOf(modifiableSet);
// 요소를 추가해 보자
try {
immutableSet.add("D");
} catch (UnsupportedOperationException e) {
System.out.println("Set을 변경할 수 없습니다!");
}
Map<String, Integer> modifiableMap = new HashMap<>();
modifiableMap.put("Alice", 30);
modifiableMap.put("Bob", 25);
Map<String, Integer> immutableMap = Map.copyOf(modifiableMap);
try {
immutableMap.put("Charlie", 28);
} catch (UnsupportedOperationException e) {
System.out.println("Map을 변경할 수 없습니다!");
}
}
}
2. copyOf 메서드의 특징과 제약
null 요소는 허용되지 않습니다
List.of, Set.of, Map.of 같은 팩토리 메서드와 마찬가지로, copyOf는 null을 요소, 키 또는 값으로 허용하지 않습니다. 원본 컬렉션/맵에 null이 있으면 복사 과정에서 NullPointerException이 발생합니다.
List<String> listWithNull = Arrays.asList("A", null, "B");
List<String> immutable = List.copyOf(listWithNull); // NullPointerException 발생!
컬렉션은 진정으로 불변입니다
어떤 add/remove/put/replace 시도도 UnsupportedOperationException을 발생시킵니다.
원본과 복사본은 연결되어 있지 않습니다
원본 컬렉션의 변경은 복사본에 영향을 주지 않으며, 그 반대도 마찬가지입니다.
이미 불변 컬렉션이라면 — 같은 객체를 반환
이미 불변 컬렉션(예: List.of(...))을 전달하면 — copyOf는 동일한 객체를 반환합니다(메모리 절약).
List<String> immutable = List.of("X", "Y");
List<String> copy = List.copyOf(immutable);
System.out.println(immutable == copy); // true
구체적 구현은 보장되지 않습니다
반환되는 컬렉션의 타입은 단지 List, Set 또는 Map입니다. 반드시 ArrayList, HashSet 또는 HashMap일 필요는 없습니다. 구현 세부사항(예: 내부 타입과 최적화)에 의존하지 마세요; 보장되는 계약은 불변성과 해당 인터페이스뿐입니다.
3. copyOf와 래퍼(unmodifiable wrappers)의 차이
Collections.unmodifiableList(list)는 기존 컬렉션 위에 래퍼를 만듭니다. 원본 컬렉션이 변경되면 그 변경이 래퍼에도 보입니다.
List.copyOf(list)는 원본과 연결되지 않은 새로운 불변 컬렉션을 생성합니다.
차이점 시연
import java.util.*;
public class WrapperVsCopyOf {
public static void main(String[] args) {
List<String> original = new ArrayList<>(List.of("A", "B"));
// 래퍼
List<String> wrapper = Collections.unmodifiableList(original);
// 복사본
List<String> copy = List.copyOf(original);
// 원본을 변경
original.add("C");
System.out.println("래퍼: " + wrapper); // [A, B, C]
System.out.println("복사본: " + copy); // [A, B]
}
}
출력:
래퍼: [A, B, C]
복사본: [A, B]
래퍼는 원본의 변경을 반영하지만, 복사본은 변하지 않습니다.
4. copyOf의 실전 활용 시나리오
메서드 반환 시 데이터 보호
public class CurrencyService {
private final List<String> currencies = new ArrayList<>(List.of("USD", "EUR", "JPY"));
public List<String> getSupportedCurrencies() {
// 아무도 결과를 변경할 수 없습니다
return List.copyOf(currencies);
}
}
이제 호출 측 코드는 반환된 목록에서 통화를 추가하거나 삭제할 수 없습니다.
애플리케이션 계층 간 전달
데이터가 계층(DAO → 서비스 → 컨트롤러) 사이를 이동할 때, 중간에서 컬렉션이 변경되지 않도록 copyOf를 사용하세요.
멀티스레드 프로그램에서의 안전한 공개
불변 컬렉션은 추가 동기화 없이도 스레드 간에 데이터를 안전하게 공유할 수 있는 편리한 방법입니다.
5. copyOf 및 불변 컬렉션 사용 시 흔한 실수
오류 № 1: null 삽입 시도. copyOf(of와 마찬가지로)는 null을 허용하지 않습니다 — 리스트/셋의 요소이든, 맵의 키/값이든. 복사 중에 NullPointerException이 발생합니다.
오류 № 2: 래퍼와 복사본을 혼동. copyOf는 독립적인 복사본을 생성합니다: 원본의 변경은 복사본에 보이지 않습니다. 반면 Collections.unmodifiableList(list)는 단지 원본 컬렉션을 감싸기만 하므로, 원본의 모든 변경이 래퍼에 반영됩니다.
오류 № 3: 불변 컬렉션을 변경하려 함. copyOf/of로 만든 컬렉션에서 add/remove/put을 호출하면 UnsupportedOperationException이 발생합니다.
오류 № 4: 맵 팩토리의 제약을 잊음. 예를 들어, Map.of()는 최대 10쌍의 키–값만 지원합니다. 더 필요하다면 Map.ofEntries(...)를 사용하거나 변경 가능한 맵을 만든 뒤 Map.copyOf를 적용하세요.
오류 № 5: 구체적 구현에 의존함. copyOf가 반드시 ArrayList나 HashSet을 반환한다고 기대하지 마세요. 보장되는 것은 인터페이스(List/Set/Map)와 불변성뿐입니다.
GO TO FULL VERSION