CodeGym /행동 /JAVA 25 SELF /Map.copyOf, Set.copyOf 및 기타 유틸리티

Map.copyOf, Set.copyOf 및 기타 유틸리티

JAVA 25 SELF
레벨 34 , 레슨 1
사용 가능

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 같은 팩토리 메서드와 마찬가지로, copyOfnull을 요소, 키 또는 값으로 허용하지 않습니다. 원본 컬렉션/맵에 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가 반드시 ArrayListHashSet을 반환한다고 기대하지 마세요. 보장되는 것은 인터페이스(List/Set/Map)와 불변성뿐입니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION