You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的合并函数

需要注意两个关键点:

  1. Arrays.asList()返回的是不可变列表,合并时无法添加元素,需替换为可变的ArrayList
  2. 合并函数要将新对象的状态列表合并到已有对象的列表中

修改后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 01:16:08