Java 8中Map收集器合并函数:将重复Key元素存入同一列表
问题:Java 8 Stream 收集Map时合并重复Key对应的列表元素
在Java 8中通过Stream创建Map时,需要将重复Key对应的元素存入同一个ItemRequest对象的itemStatuses列表中,使用Collectors.toMap(keyMapper, valueMapper, mergeFunction)方法时,无法编写正确的合并函数。以下是可复现问题的代码:
问题代码示例
MapCollector.java
public class MapCollector { public static Map<ItemType, ItemRequest> breakRequestByItemType(final ItemRequest originalRequest) { return originalRequest .getItemStatuses() .stream() .filter(e->e.getType()!= null) .collect(Collectors.toMap( ItemStatus::getType, e->ItemRequest .builder() .prop1(originalRequest.prop1) .prop2(originalRequest.prop2) .itemStatuses(Arrays.asList(e)) .build() // TODO: 需要编写合并函数,将重复Key的状态添加到itemStatuses列表中 )); } } @Data @Builder class ItemRequest{ List<ItemStatus> itemStatuses; String prop1; String prop2; } enum ItemType{ A,B,C } @AllArgsConstructor @Getter enum ItemStatus{ A_STATUS_1("some desc1",ItemType.A), A_STATUS_2("some desc2",ItemType.A), B_STATUS_1("some desc3",ItemType.B), B_STATUS_2("some desc4",ItemType.B), C_STATUS_1("some desc5",ItemType.C); private final String desc; private final ItemType type; }
pom.xml依赖
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.21.0</version> </dependency> </dependencies>
测试类MapCollectorTest.java
public class MapCollectorTest { @Test void testIt(){ final ItemRequest originalRequest = ItemRequest.builder() .itemStatuses(Arrays.asList(ItemStatus.A_STATUS_1, ItemStatus.C_STATUS_1, ItemStatus.B_STATUS_2, ItemStatus.A_STATUS_2)) .prop1("abc") .prop2("bcd") .build(); Map<ItemType, ItemRequest> expectedMap = new HashMap<ItemType, ItemRequest>(); expectedMap.put(ItemType.A, ItemRequest .builder() .itemStatuses(Arrays.asList(ItemStatus.A_STATUS_1,ItemStatus.A_STATUS_2)) .prop1("abc") .prop2("bcd") .build()); expectedMap.put(ItemType.B, ItemRequest .builder() .itemStatuses(Arrays.asList(ItemStatus.B_STATUS_2)) .prop1("abc") .prop2("bcd") .build()); expectedMap.put(ItemType.C, ItemRequest .builder() .itemStatuses(Arrays.asList(ItemStatus.C_STATUS_1)) .prop1("abc") .prop2("bcd") .build()); Assertions.assertThat(MapCollector.breakRequestByItemType(originalRequest)) .containsAllEntriesOf(expectedMap); } }
解决方案
方式一:完善Collectors.toMap的合并函数
需要注意两个关键点:
Arrays.asList()返回的是不可变列表,合并时无法添加元素,需替换为可变的ArrayList- 合并函数要将新对象的状态列表合并到已有对象的列表中
修改后的breakRequestByItemType方法:
public class MapCollector { public static Map<ItemType, ItemRequest> breakRequestByItemType(final ItemRequest originalRequest) { return originalRequest .getItemStatuses() .stream() .filter(e->e.getType()!= null) .collect(Collectors.toMap( ItemStatus::getType, e->ItemRequest .builder() .prop1(originalRequest.prop1) .prop2(originalRequest.prop2) .itemStatuses(new ArrayList<>(Arrays.asList(e))) // 改用可变ArrayList .build(), (existingReq, newReq) -> { // 将新请求的状态列表合并到已有请求中 existingReq.getItemStatuses().addAll(newReq.getItemStatuses()); return existingReq; // 返回合并后的已有对象 } )); } }
方式二:使用Collectors.groupingBy(更直观)
先按ItemType分组收集状态列表,再将每组转换为ItemRequest对象,逻辑更清晰,无需处理合并冲突:
public static Map<ItemType, ItemRequest> breakRequestByItemType(final ItemRequest originalRequest) { return originalRequest.getItemStatuses() .stream() .filter(e -> e.getType() != null) .collect(Collectors.groupingBy(ItemStatus::getType)) .entrySet() .stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> ItemRequest.builder() .prop1(originalRequest.prop1) .prop2(originalRequest.prop2) .itemStatuses(entry.getValue()) .build() )); }
两种方式均可通过测试类的断言验证结果符合预期。
内容的提问来源于stack exchange,提问作者ThrowableException
相关产品推荐
相关产品推荐

