Java

[Java] Collections Framework간 변환 총정리 - List, Set, Map, Queue

jundyu 2024. 12. 3. 18:00

Java

 

들어가며

자바로 알고리즘을 문제풀 때 컬렉션 프레임워크 간에 변환해야하는 경우가 생기는데 이때 사용하는 메서드를 정리하면 좋을 것 같아 작성하게 되었습니다. 우선, 데이터의 변환이 필요 없다는 가정 하에 정리했기 때문에 Stream API는 거의 사용하지 않았습니다. 미묘하지만 생성자에 직접 객체를 전달하는 방법이 더 빠르기 때문에 Stream API는 최대한 삼가했습니다.

 


Collections Framework

Collections Framework 계층 구조

Collections Framework의 계층 구조를 간략하게 만들어 봤습니다. 전체적인 그림을 알면 이해가 더 쉽기 때문에 알고 가면 좋겠습니다. 흰색 배경은 인터페이스를 의미하고, 보라색 배경은 인터페이스의 구현체입니다. 그리고 실선은 상속, 점선은 구현을 의미합니다.

Map은 왜 Iterable을 상속 받지 않을까?
Map은 키-값 쌍으로 데이터를 저장하도록 설계되었고, Iterable은 순차적으로 요소를 순회할 수 있는 컬렉션을 위한 인터페이스입니다. 자바의 초창기부터 Map과 Iterable을 명확히 분리했는데 이는 Map의 데이터 구조와 Iterable의 순회 방식이 다르기 때문입니다. key, value, key-value 총 3가지의 관점으로 순회가능한 Map은 아래에 나와있는 메서드를 통해 간접적으로 순회할 수 있습니다.

 

 

# List

0. Array & List

List를 다루기 전에 배열을 List로, 그리고 List를 배열로 다루는 방법을 확인하고 넘어가겠습니다.

(i) 원소가 객체 타입인 경우 - Integer, Character 등

// 선언
Integer[] array = new Integer[n];

// 배열을 List 객체로 변환
List<Integer> list = Arrays.asList(array);
// 선언
List<Integer> list = new ArrayList<>();

// List 객체를 배열로 변환
Integer[] array = list.toArray(new Integer[list.size()]);

위처럼 Integer, String 등의 객체 타입은 인스턴스 메서드를 통해 쉽게 배열과 List 간에 변환이 가능합니다. List를 배열로 변환하는 경우 배열의 크기를 원소 개수보다 크게 설정한다면 남는 인덱스는 전부 null로 할당됩니다.

 

(ii) 원소가 원시 타입인 경우 - int, char 등

// 선언
int[] arr = {1, 2, 3};

// 원시 타입 배열을 List 객체로 변환
List<Integer> list = Arrays.stream(arr).boxed().toList();
// 선언
List<Integer> list = Arrays.asList(1, 2, 3);

// List를 원시 타입의 배열로 변환
int[] intArray = list.stream().mapToInt(Integer::intValue).toArray();

 

자바의 제네릭은 Object 객체를 기반으로 설계되어 있고, 런타임(Runtime) 시 타입이 소거 되기 때문에 원시 타입을 사용할 수 없습니다. 원소의 데이터 타입을 하나씩 변환해야하기 때문에 Stream API를 사용했습니다.

 

제네릭의 원시 타입(primitive)과 타입 소거(Type Erasure)에 관한 내용은 아래의 글을 추천합니다.

 

[Java] 왜 제네릭은 원시 타입을 사용할 수 없는가

출처ChatGPT1. 제네릭의 작동 방식과 타입 소거(Type Erasure)관련 글 -> [Java] 제네릭의 타입 소거 Type Erasure제네릭은 컴파일 시간에 타입을 검사하고, 런타임에는 타입 정보를 유지하지 않는다. 즉, 제

lifework-archive-reservoir.tistory.com

1. List to Set

// 선언
List<String> list = new ArrayList<>();

// List를 Set 객체로 변환
Set<String> set = new HashSet<>(list);

List를 집합으로 변환하는 것은 아주 간단합니다. 그냥 Set 인스턴스를 생성할 때 List 객체를 전달합니다.

2. List to Map

// 선언
List<String> list = new ArrayList<>();

// List를 Map 객체로 변환
Map<Integer, String> map = IntStream.range(0, list.size())
                                    .boxed()
                                    .collect(Collectors.toMap(i -> i, list::get));

Map 객체는 Key-Value 쌍으로 저장되기 때문에 위에선 예시로 list의 인덱스를 Key로 사용했습니다. List를 Map으로 변환할때도 Stream API를 사용합니다. Stream API에 관한 글이 아니기 때문에 Stream에 대해선 다음 글에서 바로 다뤄보겠습니다.

3. List to Queue

// 선언
List<String> list = new ArrayList<>();

// List를 LinkedList 객체로 변환
Queue<String> lq = new LinkedList<>(list);
// List를 PriorityQueue 객체로 변환
Queue<String> pq = new PriorityQueue<>();

LinkedList와 PriorityQueue는 List와 Queue를 구현하기 때문에 list를 전달하면 쉽게 변환할 수 있습니다. 이때, PriorityQueue는 변환과 동시에 자연 정렬까지 이뤄집니다.

 

 

# Set

1. Set to List

// 선언
Set<String> set = new HashSet<>();

// Set을 List로 변환
List<String> list = new ArrayList<>(set);

Set을 List로 변환하는 경우 Set 구현체별로 정렬 방식이 다릅니다.

  • HashSet : 요소의 순서 보장하지 않음
  • LinkedHashSet : 삽입 순서대로 정렬
  • TreeSet : 기본적으로 오름차순으로 정

2. Set to Map

// 선언
Set<String> set = new HashSet<>();

// Set을 Map 객체로 변환
Map<Integer, String> map = IntStream.range(0, list.size())
                                            .boxed()
                                            .collect(Collectors.toMap(i -> i, list::get));

Set을 Map 객체로 바꿀 일은 잘 없겠지만 인덱스를 Key로 지정해서 Map 객체를 만들 수 있습니다.

3. Set to Queue

// 선언
Set<String> set = new HashSet<>();

// Set을 Queue 객체로 변환
Queue<String> queue = new LinkedList<>(set);

 

 

# Map

Map은 Key-Value로 이루어져 있기 때문에 선택적으로 변환 가능합니다. Map을 List, Set, Queue로 변환하는 방법은 모두 같습니다. key만 가져올 땐 keySet(), value만 사용할 땐 values(), 키값 쌍으로 가져올 땐 entrySet(()을 쓰면 됩니다.

1. Map to List

// 선언
Map<Integer, String> map = new HashMap<>();

// Key만 List로 변환
List<Integer> keys = new ArrayList<>(map.keySet());
// Value만 List로 변환
List<Integer> keys = new ArrayList<>(map.values());
// Key-Value를 List로 변환
List<Map.Entry<Integer, String>> entries = new ArrayList<>(map.entrySet());

2. Map to Set

// 선언
Map<Integer, String> map = new HashMap<>();

// Key만 Set로 변환
Set<Integer> keySet = new HashSet<>(map.keySet());
// Value만 Set로 변환
Set<String> valueSet = new HashSet<>(map.values());
// Key-Value를 Set로 변환
Set<Map.Entry<Integer, String>> entrySet = new HashSet<>(map.entrySet());

3. Map to Queue

// 선언
Map<Integer, String> map = new HashMap<>();

// Key만 Queue로 변환
Queue<Integer> keyQueue = new LinkedList<>(map.keySet());
// Value만 Queue로 변환
Queue<String> valueQueue = new LinkedList<>(map.values());
// Key-Value를 Queue로 변환
Queue<Map.Entry<Integer, String>> entryQueue = new LinkedList<>(map.entrySet());

 

 

# Queue

1. Queue to List

// 선언
Queue<String> queue = new LinkedList<>();

// Queue를 List 객체로 변환
List<String> list = new ArrayList<>(queue);

위처럼 생성자에 queue를 전달해서 간단하게 변환할 수 있습니다.

2. Queue to Set

// 선언
Queue<String> queue = new LinkedList<>();

// Queue를 Set 객체로 변환
Set<String> set = new HashSet<>(queue);

3. Queue to Map

// 선언
Queue<String> queue = new LinkedList<>();

// Queue를 Map 객체로 변환
Map<Integer, String> map = queue.stream()
                                .collect(Collectors.toMap(item -> index.getAndIncrement(),
                                                                  item -> item));

Queue를 Map으로 변환할 때도 마찬가지로 인덱스를 Key로 사용했습니다.

 

 

.

.

.

 

마치며

알고리즘 공부할 때 제대로 정리를 안 하니까 금방 잊어버려서 시간 할애해서 정리해봤습니다. 원래 제 블로그에서 모음집 같은 글은 지양하고 있지만 이건 앞으로 여러모로 도움 될 것 같아 정리했습니다!