Collectors.toMap 사용시 중복 key가 존재하는 경우 IllegalStateException 이 발생한다.
@Test public void duplicateKeyError() {
List<Category> categories = Lists.newArrayList(
Category.builder().categoryId("KO_01").languageCode("ko").build(),
Category.builder().categoryId("KO_02").languageCode("ko").build()
);
Map<String, Category> categoryMap = categories.stream().collect(toMap(Category::getLanguageCode, Function.identity()));
assertThat(categoryMap.size(), is(1));
}
|
java.lang.IllegalStateException: Duplicate key Category(categoryId=KO_01, languageCode=ko, categoryName=null)
at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
at java.util.HashMap.merge(HashMap.java:1253)
at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at tomining.java.stream.ToMapExample.duplicateKeyError(ToMapExample.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
|
왜 그럴까?
테스트 코드에서 사용한 toMap() 인터페이스를 살펴보자.
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
|
private static <T> BinaryOperator<T> throwingMerger() {
return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}
|
mergeFunction에 throwingMerger()를 전달하고 있는 것이 확인된다.
이 부분이 IllegalStateException의 원인이다.
즉, toMap()은 기본적으로 중복이 있는 경우 Exception을 반환하게 된다. HashMap의 Key 충돌을 생각하면 쉽다.
만약 Key 중복시 기준을 제시해 줄 수 있다면 mergeFunction을 명시적으로 만들어 전달할 수도 있다.
@Test public void mergeFunction() {
List<Category> categories = Lists.newArrayList(
Category.builder().categoryId("KO_01").languageCode("ko").build(),
Category.builder().categoryId("KO_02").languageCode("ko").build()
);
Map<String, Category> categoryMap = categories.stream().collect(toMap(Category::getLanguageCode, Function.identity(), firstPriority()));
assertThat(categoryMap.size(), is(1));
}
private <T> BinaryOperator<T> firstPriority() {
return (u,v) -> { return u; };
}
|
위에서 구현된 mergeFunction은 중복 키가 발생한 경우 첫 번째 아이템으로 유지하는 경우이다.
동일한 기조로 Collectors.groupingBy() 라는 것을 활용할 수도 있다.
@Test public void groupingByExample() {
List<Category> categories = Lists.newArrayList(
Category.builder().categoryId("KO_01").languageCode("ko").build(),
Category.builder().categoryId("KO_02").languageCode("ko").build()
);
Map<String, List<Category>> categoryMap = categories.stream().collect(groupingBy(Category::getLanguageCode, toList()));
assertThat(categoryMap.size(), is(1));
}
|
'Programing > Java' 카테고리의 다른 글
Java에서 문자열에 포함된 HTML 태그들 제거하기 (0) | 2018.02.08 |
---|---|
Jackson @JsonView 사용기 (0) | 2017.09.27 |
How to use exceptions effectively? (어떻게 Exception을 효율적으로 사용할까?) (0) | 2017.08.28 |
Java에서 String과 new String()의 차이는? (2) | 2017.08.24 |
Jackson을 이용한 org.joda.time.DateTime 파싱하기 (0) | 2017.07.27 |