首页 > Java > java教程 > 正文

Java Stream API:数据分组与嵌套JSON列表转换实践

DDD
发布: 2025-09-13 12:03:36
原创
456人浏览过

Java Stream API:数据分组与嵌套JSON列表转换实践

本文详细讲解如何利用Java Stream API将扁平化的数据列表(如数据库查询结果)进行高效分组,并将其转换为包含嵌套列表的复杂对象结构,最终便于序列化为JSON格式。通过groupingBy和mapping等操作,实现数据聚合与重塑,提升代码的简洁性和可读性。

引言

在现代应用开发中,我们经常需要处理来自数据库或其他数据源的扁平化数据列表。然而,为了满足前端展示、api接口或下游系统的数据消费需求,这些数据往往需要被重塑为更具结构化、层次化的形式,例如按某个字段分组并将其相关属性聚合为一个嵌套列表。java 8引入的stream api为这种复杂的数据转换提供了强大而优雅的解决方案,极大地简化了代码并提高了可读性。本教程将深入探讨如何利用stream api实现这一目标,最终输出符合特定嵌套结构的json数据。

数据模型定义

首先,我们根据需求定义原始数据模型和目标数据模型。

1. 原始响应类 (Response)

这通常是数据库查询结果映射到的对象,包含一个主键ID和两个描述性字段。

public interface Response {
    Long getId();
    String getSData();
    String getSName();
}

// 为了方便演示和创建实例,我们提供一个实现类
public static class ResponseImpl implements Response {
    private Long id;
    private String sData;
    private String sName;

    public ResponseImpl(Long id, String sData, String sName) {
        this.id = id;
        this.sData = sData;
        this.sName = sName;
    }

    @Override
    public Long getId() { return id; }
    @Override
    public String getSData() { return sData; }
    @Override
    public String getSName() { return sName; }

    @Override
    public String toString() {
        return "ResponseImpl{id=" + id + ", sData='" + sData + "', sName='" + sName + "'}";
    }
}
登录后复制

2. 子数据类 (SubData)

立即学习Java免费学习笔记(深入)”;

这是分组后嵌套在主对象中的列表元素,它包含了原始Response中除id以外的两个字段。

public static class SubData {
    private String sData;
    private String sName;

    public SubData(String sData, String sName) {
        this.sData = sData;
        this.sName = sName;
    }

    // Getters and Setters for JSON serialization
    public String getSData() { return sData; }
    public void setSData(String sData) { this.sData = sData; }
    public String getSName() { return sName; }
    public void setSName(String sName) { this.sName = sName; }

    @Override
    public String toString() {
        return "SubData{sData='" + sData + "', sName='" + sName + "'}";
    }
}
登录后复制

3. 目标响应类 (NewResponse)

这是我们希望最终得到的数据结构,它包含原始的id和一个SubData对象的列表。

public static class NewResponse {
    private Long id;
    private List<SubData> subDataList;

    public NewResponse(Long id, List<SubData> subDataList) {
        this.id = id;
        this.subDataList = subDataList;
    }

    // Getters and Setters for JSON serialization
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public List<SubData> getSubDataList() { return subDataList; }
    public void setSubDataList(List<SubData> subDataList) { this.subDataList = subDataList; }

    @Override
    public String toString() {
        return "NewResponse{id=" + id + ", subDataList=" + subDataList + "}";
    }
}
登录后复制

原始数据准备

为了演示,我们创建一份模拟的原始Response列表,其结构与问题描述中的JSON数据一致。

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class DataGroupingTutorial {

    // ... (定义上面提到的 ResponseImpl, SubData, NewResponse 类) ...

    public static void main(String[] args) {
        List<Response> responses = Arrays.asList(
            new ResponseImpl(1L, "UK", "X"),
            new ResponseImpl(1L, "FR", "X"), // 修正:原始数据中sData和sName是分开的,这里模拟成这样
            new ResponseImpl(2L, "UK", "Y"),
            new ResponseImpl(2L, "FR", "Y"),
            new ResponseImpl(4L, "EU", "X"),
            new ResponseImpl(4L, "Others", "O")
        );

        System.out.println("原始数据:");
        responses.forEach(System.out::println);
        System.out.println("\n---");
    }
}
登录后复制

注:根据原始问题描述的JSON示例 {"id":1,"sData":"UK,FR","sName":"X},sData字段似乎包含了多个值。但在Response接口中sData是String类型,且期望输出的SubData中sData也是单个值。为了符合教程的通用性,这里假设sData和sName是单个值,若sData本身就是逗号分隔的字符串,则在SubData构造时需要额外处理。本教程以最直接的映射方式进行。

Find JSON Path Online
Find JSON Path Online

Easily find JSON paths within JSON objects using our intuitive Json Path Finder

Find JSON Path Online 30
查看详情 Find JSON Path Online

核心转换逻辑

我们将分两步使用Stream API来完成数据转换。

1. 步骤一:初步分组与子数据映射

这一步的目标是将原始List<Response>按照id进行分组,并且在分组的同时,将每个Response对象转换为SubData对象,并收集成一个列表。

// ... (接上面的main方法) ...

        Map<Long, List<SubData>> grouped = responses.stream()
                .collect(Collectors.groupingBy(
                        Response::getId, // 按Response的ID进行分组
                        Collectors.mapping(
                                r -> new SubData(r.getSData(), r.getSName()), // 将每个Response映射为SubData
                                Collectors.toList() // 将映射后的SubData收集成一个列表
                        )
                ));

        System.out.println("步骤一:分组后的中间结果 (Map<Long, List<SubData>>):");
        grouped.forEach((id, subDataList) -> System.out.println("ID: " + id + ", SubDataList: " + subDataList));
        System.out.println("\n---");
登录后复制
  • Collectors.groupingBy(Response::getId, ...): 这是Stream API中用于分组的核心收集器。它接收一个分类函数(这里是Response::getId,表示按id字段分组),以及一个下游收集器。
  • Collectors.mapping(r -> new SubData(r.getSData(), r.getSName()), Collectors.toList()): 这是groupingBy的下游收集器。
    • Collectors.mapping(...):在分组内部,将每个Response对象通过提供的映射函数r -> new SubData(r.getSData(), r.getSName())转换为一个新的SubData对象。
    • Collectors.toList():将所有转换后的SubData对象收集到一个新的List中。

经过这一步,我们得到了一个Map<Long, List<SubData>>,其中key是原始Response的id,value是该id下所有对应的SubData对象的列表。

2. 步骤二:将分组结果转换为目标对象列表

现在我们有了一个Map<Long, List<SubData>>,我们需要将其转换为List<NewResponse>。这可以通过遍历Map的entrySet()并进行映射来完成。

// ... (接上面的main方法) ...

        List<NewResponse> finalResult = grouped.entrySet()
                .stream() // 将Map的EntrySet转换为Stream
                .map(entry -> new NewResponse(entry.getKey(), entry.getValue())) // 将每个Entry映射为NewResponse对象
                .collect(Collectors.toList()); // 收集成List<NewResponse>

        System.out.println("步骤二:最终结果 (List<NewResponse>):");
        finalResult.forEach(System.out::println);
        System.out.println("\n---");
登录后复制
  • grouped.entrySet().stream(): 获取Map的键值对集合(Set<Map.Entry<Long, List<SubData>>>),并将其转换为Stream。
  • map(entry -> new NewResponse(entry.getKey(), entry.getValue())): 对Stream中的每个Map.Entry对象进行映射。entry.getKey()提供了id,entry.getValue()提供了List<SubData>,正好可以用来构造NewResponse对象。
  • collect(Collectors.toList()): 将所有映射后的NewResponse对象收集到一个新的List中。

至此,我们成功地将扁平化的List<Response>转换成了List<NewResponse>,其结构完全符合我们预期的嵌套JSON格式。

链式操作优化 (可选)

如果你希望代码更简洁,可以将上述两个步骤合并为一个Stream链。虽然这会减少中间变量,但对于非常复杂的逻辑,可能会略微降低可读性。

// ... (接上面的main方法) ...

        System.out.println("链式操作优化后的结果 (List<NewResponse>):");
        List<NewResponse> chainedResult = responses.stream()
                .collect(Collectors.groupingBy(
                        Response::getId,
                        Collectors.mapping(
                                r -> new SubData(r.getSData(), r.getSName()),
                                Collectors.toList()
                        )
                )) // 结果是 Map<Long, List<SubData>>
                .entrySet() // 获取Map的EntrySet
                .stream() // 将EntrySet转换为Stream
                .map(entry -> new NewResponse(entry.getKey(), entry.getValue())) // 映射为NewResponse
                .collect(Collectors.toList()); // 收集最终结果

        chainedResult.forEach(System.out::println);
        System.out.println("\n---");
登录后复制

完整示例代码

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class DataGroupingTutorial {

    // 原始响应接口
    public interface Response {
        Long getId();
        String getSData();
        String getSName();
    }

    // 原始响应接口的实现类
    public static class ResponseImpl implements Response {
        private Long id;
        private String sData;
        private String sName;

        public ResponseImpl(Long id, String sData, String sName) {
            this.id = id;
            this.sData = sData;
            this.sName = sName;
        }

        @Override
        public Long getId() { return id; }
        @Override
        public String getSData() { return sData; }
        @Override
        public String getSName() { return sName; }

        @Override
        public String toString() {
            return "ResponseImpl{id=" + id + ", sData='" + sData + "', sName='" + sName + "'}";
        }
    }

    // 子数据类
    public static class SubData {
        private String sData;
        private String sName;

        public SubData(String sData, String sName) {
            this.sData = sData;
            this.sName = sName;
        }

        // Getters and Setters for JSON serialization
        public String getSData() { return sData; }
        public void setSData(String sData) { this.sData = sData; }
        public String getSName() { return sName; }
        public void setSName(String sName) { this.sName = sName; }

        @Override
        public String toString() {
            return "SubData{sData='" + sData + "', sName='" + sName + "'}";
        }
    }

    // 目标响应类
    public static class NewResponse {
        private Long id;
        private List<SubData> subDataList;

        public NewResponse(Long id, List<SubData> subDataList) {
            this.id = id;
            this.subDataList = subDataList;
        }

        // Getters and Setters for JSON serialization
        public Long getId() { return id; }
        public void setId(Long id) { this.id = id; }
        public List<SubData> getSubDataList() { return subDataList; }
        public void setSubDataList(List<SubData> subDataList) { this.subDataList = subDataList; }

        @Override
        public String toString() {
            return "NewResponse{id=" + id + ", subDataList=" + subDataList + "}";
        }
    }

    public static void main(String[] args) {
        // 模拟原始数据
        List<Response> responses = Arrays.asList(
            new ResponseImpl(1L, "UK", "X"),
            new ResponseImpl(1L, "FR", "X"),
            new ResponseImpl(2L, "UK", "Y"),
            new ResponseImpl(2L, "FR", "Y"),
            new ResponseImpl(4L, "EU", "X"),
            new ResponseImpl(4L, "Others", "O")
        );

        System.out.println("原始数据:");
        responses.forEach(System.out::println);
        System.out.println("\n---");

        // 步骤一:初步分组与子数据映射
        Map<Long, List<SubData>> grouped = responses.
登录后复制

以上就是Java Stream API:数据分组与嵌套JSON列表转换实践的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号