Search code examples
javajava-8steam

Java: how to convert a List<T> to a Map<String,Object>


I want to construct a chapter directory. My data structure is as follow.

private Map<String, ChapterPage> chapterPageRel;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ChapterPage {
    @ApiModelProperty(value = "page list")
    private List<Integer> page;
    @ApiModelProperty(value = "page image list")
    private List<String> chapterImageId;
}

My database data is as follow.

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "tb_electronic_material_resource_rel")
public class ElectronicMaterialResourceRelEntity {

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @TableField(value = "material_id")
    private Long materialId;

    @TableField(value = "chapter_image_id")
    private String chapterImageId;

    @TableField(value = "chapter_name")
    private String chapterName;

    @TableField(value = "page")
    private Integer page;
}

I want to get each chapterName of the corresponding page Numbers and pictures, in fact, I get a strange map, as shown below.

List<ElectronicMaterialResourceRelEntity> materialResourceRelEntities = xxxx;
Map<String, Map<Integer, String>> collect =
    materialResourceRelEntities
    .stream()
    .collect(Collectors.groupingBy(ElectronicMaterialResourceRelEntity::getChapterName, Collectors.toMap(ElectronicMaterialResourceRelEntity::getSort, ElectronicMaterialResourceRelEntity::chapterImageId)));

But I want to ask, how to collect for a class like Map<String,ChapterPage>.


Solution

  • A loop is probably better here, unless you need to collect to such ChapterPage objects in multiple places in your code or your input is already a stream and you want to avoid materializing it into a list first. Just to demonstrate how this could still be done, this is an example using a custom downstream Collector:

    Map<String, ChapterPage> res = input.stream()
            .collect(Collectors.groupingBy(ElectronicMaterialResourceRelEntity::chapterName, 
                    Collector.of(
                            ChapterPage::new,
                            (c, e) -> {
                                c.getPage().add(e.page());
                                c.getChapterImage().add(e.chapterImageId());
                            },
                            (c1, c2) -> {
                                c1.getPage().addAll(c2.getPage());
                                c2.getChapterImage().addAll(c2.getChapterImage());
                                return c1;
                            }
                            )));
    

    Getters and constructors differ from your code, I used a record for ElectronicMaterialResourceRelEntity and a POJO without annotation magic for ChapterPage in my test.


    By "loop", I don't necessarily mean a traditional loop, but a somewhat less functional approach directly using a mutable HashMap:

    Map<String, ChapterPage> res = new HashMap<>();
    input.forEach(e -> {
        ChapterPage c = res.computeIfAbsent(e.chapterName(), k -> new ChapterPage());
        c.getPage().add(e.page());
        c.getChapterImage().add(e.chapterImageId());
    });
    

    produces the same result in a less verbose way.