🧐 Collectors.toMap이 뭔가요?
Stream을 사용하는 경우, 최종 연산으로 collect()를 사용하는 경우, Stream의 요소들을 수집하여 특정한 자료구조로 변환할 수 있습니다.
이때 Map으로 변환하기 위해서는 Collectors.toMap을 사용합니다.
흔히 사용하는 toMap은 다음과 같습니다.
List<String> strings = Arrays.asList("apple", "banana", "pear");
Map<Integer, String> map = strings.stream()
.collect(Collectors.toMap(String::length, Function.identity()));
System.out.println(map); // 결과: {4=pear, 5=apple, 6=banana}
그러나 위 코드에는 문제점이 있습니다.
바로 key의 중복이 발생한다면, 오류가 발생한다는 것인데요, 살펴보도록 하겠습니다.
List<String> strings = Arrays.asList("apple", "banana", "carrot", "pear");
Map<Integer, String> map = strings.stream()
.collect(Collectors.toMap(String::length, Function.identity()));
발생하는 오류는 다음과 같습니다.
Duplicate key 6 (attempted merging values banana and carrot) java.lang.IllegalStateException: Duplicate key 6 (attempted merging values banana and carrot)
이제 이를 해결해 보도록 하겠습니다.
🧐 toMap() 시 Key의 중복을 해결하기
toMap()은 다양한 시그니처를 가지고 있는데요, 다음 시그니처에 해당하는 toMap()을 사용하여 키의 중복을 해결할 수 있습니다.
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
// ...
}
이전에 사용했던 toMap보다, 파라미터가 1개 더 늘어났으며, 해당 파라미터의 이름은 mergeFunction이라 적혀있습니다.
📕 mergeFunction() 이 뭔가요?
toMap의 mergeFunction매개변수는 다음과 같이 설명되어 있습니다.
a merge function, used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction)
즉 mergeFunction은 동일한 키로 인해 충돌(collision)이 발생했을 때, 어떠한 value를 취할 것인지 결정할 때 사용됩니다.
예를 들어, 무조건 새로운 값으로 덮어쓰려는 경우 다음과 같이 작성할 수 있습니다.
(existingValue, newValue) -> newValue;
이제 mergeFunction을 사용하여, 위에서 발생했던 Key의 충돌로 인한 오류를 해결해 보도록 하겠습니다.
아래는 충돌 발생 시, 이전의 값을 새로 들어온 값으로 대체하는 코드입니다.
List<String> strings = Arrays.asList("apple", "banana", "carrot", "pear");
Map<Integer, String> map = strings.stream()
.collect(Collectors.toMap(
String::length,
Function.identity(),
(oldVal, newVal) -> newVal
));
System.out.println(map); // {4=pear, 5=apple, 6=carrot}
만약 처음 들어온 값을 유지하고 싶다면 다음과 같이 작성할 수 있습니다.
List<String> strings = Arrays.asList("apple", "banana", "carrot", "pear");
Map<Integer, String> map = strings.stream()
.collect(Collectors.toMap(
String::length,
Function.identity(),
(oldVal, newVal) -> oldVal
));
System.out.println(map); // {4=pear, 5=apple, 6=banana}
또는 다음과 같이, 임의의 값을 반환할 수 있습니다.
List<String> strings = Arrays.asList("apple", "banana", "carrot", "pear");
Map<Integer, String> map = strings.stream()
.collect(Collectors.toMap(
String::length,
Function.identity(),
(oldVal, newVal) -> "말랑"
));
System.out.println(map); // {4=pear, 5=apple, 6=말랑}
🧐 toMap() 시 HashMap 이외의 다른 Map을 사용하는 방법
위의 예시에서 반환되는 Map은 항상 hashMap입니다.
그러나 예를 들어, Key의 순서를 유지하고 싶은 경우 LinkedHashMap 등을 사용할 수 있습니다.
이런 경우 다음 시그니처를 가지는 toMap()을 사용할 수 있습니다.
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapFactory) {
}
마지막 파라미터로 Supplier<M> mapFactory가 추가되었는데요, 이에 대한 설명은 다음과 같습니다.
mapFactory – a supplier providing a new empty Map into which the results will be inserted
즉 반환될 때 사용할 empty Map을 제공하여야 합니다.
예를 들어 순서를 유지하기 위해 LinkedHashMap을 사용하고자 한다면, 이전 코드를 다음과 같이 변경할 수 있습니다.
List<String> strings = Arrays.asList("apple", "banana", "carrot", "pear");
Map<Integer, String> map = strings.stream()
.collect(Collectors.toMap(
String::length,
Function.identity(),
(oldVal, newVal) -> newVal,
LinkedHashMap::new
));
System.out.println(map); // {5=apple, 6=carrot, 4=pear}
API에는 empty Map을 제공해야 한다고 되어있지만, 꼭 그럴 필요는 없습니다.
즉, 아래와 같이 비어있지 않은 값을 제공할 수 있습니다.
List<String> strings = Arrays.asList("apple", "banana", "carrot", "pear");
Map<Integer, String> notEmptyMap = new LinkedHashMap<>();
notEmptyMap.put(100, "안녕하세요? 말랑입니다.");
Map<Integer, String> map = strings.stream()
.collect(Collectors.toMap(
String::length,
Function.identity(),
(oldVal, newVal) -> newVal,
() -> notEmptyMap
));
System.out.println(map); // {100=안녕하세요? 말랑입니다., 5=apple, 6=carrot, 4=pear}
'☕️ Java > 기본' 카테고리의 다른 글
[Java] Lombok Getter를 Recored Style로 만들기 (0) | 2023.08.11 |
---|---|
[Java] Varargs(가변인수)와 Heap Pollution(힙 오염) (2) | 2023.03.21 |
[Java] 얕은 복사, 방어적 복사, 깊은 복사 (12) | 2023.02.23 |
[Java] EnumMap 에 대하여 (0) | 2023.02.18 |
[Java] groupingBy를 통해 동일한 자료의 개수 구하기 (0) | 2022.11.12 |