首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java 8 Stream 流又臭又长?组长推荐我使用 JDFrame 框架,太舒坦了!!

Java 8 Stream 流又臭又长?组长推荐我使用 JDFrame 框架,太舒坦了!!

作者头像
码哥字节
发布2025-07-24 10:45:50
发布2025-07-24 10:45:50
3.8K00
代码可运行
举报
文章被收录于专栏:Java 技术栈Java 技术栈
运行总次数:0
代码可运行

你好,见字如面。我是《Redis 高手心法》作者码哥,腾讯云架构师同盟深圳区理事会成员、InfoQ 签约作者,是一个手持菜刀砍电线,一路火花带闪电的靓仔。

JDFrame 是一款专为 Java 开发者设计的轻量级数据处理工具性框架,其核心价值在于用链式 API 实现 SQL 语义化操作, 本质是对 steam 流的简化、增强和语义化,从而提供更加强大的流式处理能力。

环境准备

添加 Maven 依赖

代码语言:javascript
代码运行次数:0
运行
复制
<dependency>
    <groupId>io.github.burukeyou</groupId>
    <artifactId>jdframe</artifactId>
    <version>0.2.0</version>
</dependency>

(where 过滤)SQL 式的数据筛选

在原生 stream 流中,如果我们要进行数据筛选,只能通过 fliter 方法进行过滤,但是 JDFrame 封装了常用的过滤逻辑,包括范围查询、大于等于、in 查询、模糊查询等等, 能在一定程度上简化手写 filter 的逻辑和提升语义性, 常用的筛选如下

代码语言:javascript
代码运行次数:0
运行
复制
SDFrame.read(studentList)
        .whereBetween(Student::getAge,1,8) // 过滤年龄在[1,8]岁的
        .whereNotNull(Student::getName) // 过滤名字不为空的数据, 兼容了空字符串''的判断
        .whereGt(Student::getAge,4)    // 过滤年龄大于4岁
        .whereGe(Student::getAge,4)   // 过滤年龄大于等于3岁
        .whereIn(Student::getAge, Arrays.asList(7,9)) // 过滤年龄为7岁 或者 9岁的数据
        .whereNotIn(Student::getAge, Arrays.asList(7,9)) // 过滤年龄不为7岁 或者 9岁的数据
        .whereEq(Student::getAge,4) // 过滤年龄等于4岁的数据
        .whereNotEq(Student::getAge,4) // 过滤年龄不等于4岁的数据
        .whereLike(Student::getName,"abc") // 模糊查询,等价于 like "%abc%"

(分组聚合)学校成绩排名统计

需求: 统计每个学校在 10-26 岁年龄段学生的总分数,并筛选总分 ≥800 的学校,然后取总分最多的前十名学校

如果用 SQL 实现,我们通常会是下面这样. 分组求和即可

代码语言:javascript
代码运行次数:0
运行
复制
select school, sum(score)
from student
where age >= 10and age <= 26and age isnotnull
groupby school
havingsum(score) >= 800
orderbysum(score) desc
limit10

那同样的,下面我们来看如何用 JDFrame 来实现

代码语言:javascript
代码运行次数:0
运行
复制
// 假设有以下学生数据列表
List<Student> studentList = new ArrayList<>();

// 数据处理
List<FI2<String, BigDecimal>> sdf2 = SDFrame.read(studentList) // 转换成DataFrame模型
                .whereNotNull(Student::getAge)    // 过滤年龄不为null的
                .whereBetween(Student::getAge,10,26)   // 获取年龄在10到26岁之间的
                .groupBySum(Student::getSchool, Student::getScore) // 按照学校分组求和计算合计分数
                .whereGe(FI2::getC2,new BigDecimal(800)) // 过滤合计分数大于等于800的数据
                .sortDesc(FI2::getC2) // 按照分组后的合计分数降序排序
                .cutFirst(10)    // 截取前10名
                .toLists();     // 转换成List拿到结果

在这里我们主要用到了 JDFrame 的分组求和函数groupBySum, 它有两个参数,第一个参数表示分组的字段,也就是对学校分组,第二个参数表示求和的字段,也就是对成绩求和。执行完之后会得到一个固定的列表对象 List<FI2<String, BigDecimal>>, 用来装载我们统计后的矩阵列表结果。 然后我们继续在分组求和的基础上继续 对数据对象 List<FI2<String, BigDecimal>> 做进一步的筛选排序取前十名等操作。

在这里先介绍下我最新的点击查看专栏介绍 -》《互联网大厂面试高手心法 58 讲》专栏,原价 58元,早鸟价 10 元,现在已经涨价到 19元满 300 人立马涨价,不要再错过。

图片
图片

FI2 是个啥对象我们打开源码看一下

代码语言:javascript
代码运行次数:0
运行
复制
public class FI2<T1, T2> {

    private T1 c1;
    private T2 c2;
}

可以发现只是一个有两个泛型字段 c1、c2 的普通对象而已。 通常用来表示存储一行的结果。 c1 就表示存储第一列的结果,c2 存储第二列的结果。 类似于我们统计后的数据列表和 FI2 对应关系如下

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

可以发现它的整理链式调用语义和 SQL 语法是基本一样的,如果让我们用原生 stream 流或者手写 Java 代码是实现,想必又是一段不少的代码量。

(爆炸函数)爱好统计

需求: 有以下列表数据,统计出每个爱好有多少人喜欢,然后统计前 3 名的爱好, 同时过滤掉爱好有桌球的人不参与统计

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

接下来我们看看用 JDFrame 如何实现

代码语言:javascript
代码运行次数:0
运行
复制
@Test
public void test99() {
    List<User> list = new ArrayList<>();
        list.add(new User("A","[篮球,足球,电影]"));
        list.add(new User("B","[篮球,唱歌,电影]"));
        list.add(new User("C","[足球,电影]"));
        list.add(new User("D","[羽毛球,足球]"));
        list.add(new User("E","[足球,桌球]"));

        // Map<爱好,该爱好的用户人数>
        Map<String, Long> stats = SDFrame.read(list)
                .explodeString(User::getHobby, User::setHobby, ",") // 将爱好字段切割,变成多行
                .whereNotEq(User::getHobby, "桌球") //  过滤 爱好 不等于 桌球的用户
                .groupByCount(User::getHobby)// 按照爱好分组,并统计组内人数
                .sortDesc(FI2::getC2) // 分组后按照组内人数排序
                .cutFirst(3)  // 取排名前3的爱好
                .toMap(FI2::getC1, FI2::getC2); // 转换成Map输出
}


@AllArgsConstructor
@Data
publicstaticclass User {
    private String name;
    private String hobby;
}

接下来我们逐步分析下 JDFrame 是如何实现这一需求的

首先是调用了explodeString爆炸函数, 它的作用就是会将指定的字段(User::getHoby)按照指定的分隔符(",")进行切割, 然后将切割后每个值,重新生成一个 User 对象并添加到集合中。

如果用 excel 来表示,那么执行explodeString函数后的数据列表如下, 原来 5 行的数据列表,经过“炸开”后变成了 12 行

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

类似于下面伪代码, 可以将一行数据切割后变成多行的数据

代码语言:javascript
代码运行次数:0
运行
复制
        List<User> newUserList = new ArrayList<>();
        // 遍历每个用户
        for (User user : list) {
            // 获取用户爱好
            String hobby = user.getHobby();
            // 将爱好切割
            String[] hobbyArr = hobby.substring(1, hobby.length() - 1).split(",");
            //
            List<User> newList = Arrays.stream(hobbyArr).map(hb -> new User(user.getName(), hb)).collect(Collectors.toList());
            newUserList.addAll(newList);
        }
        list.addAll(newUserList);

原始数据经过炸开后,其实就非常方便可以进行数据的精确过滤,分组统计和排名了。

然后 执行 groupByCount 分组求数量函数后,数据列表如下

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

之后我们根据 c2字段(人数)进行排序然后取前 3 即可,最终得到我们期望的数据。

(窗口函数)TopN 问题

需求: 统计每个学生成绩排名前 3 的课程

如果用 SQL 实现, 我们通常会采用下面的窗口函数实现

代码语言:javascript
代码运行次数:0
运行
复制
SELECT * FROM (
    SELECT *,
        DENSE_RANK() OVER(PARTITION BY 学生id ORDER BY 成绩 DESC) AS ranking
    FROM 学生成绩表
) t
WHERE ranking <= 3;

下面我们看看如何同 JDFrame 实现

代码语言:javascript
代码运行次数:0
运行
复制
   @Data
    @AllArgsConstructor
    @NoArgsConstructor
    publicstaticclass User {
        privateint id;
        private String name;
        private String subject;
        private Integer score;
    }

    @Test
    public void test() {
        // 构建测试数据
        List<User> studentList = Arrays.asList(
                new User(1, "张三", "语文",90),
                new User(1, "张三","数学", 88),
                new User(1, "张三","英语", 100),
                new User(1, "张三","化学", 30),
                new User(1, "张三","物理", 20),

                new User(2, "李四", "语文",50),
                new User(2, "李四","音乐", 68),
                new User(2, "李四","英语", 90),
                new User(2, "李四","科学", 80),
                new User(2, "李四","物理", 70),

                new User(3, "王五", "历史",12),
                new User(3, "王五","数学", 89),
                new User(3, "王五","地理", 93),
                new User(3, "王五","化学", 38),
                new User(3, "王五","物理", 69)
        );

     // 统计每个学生成绩排名前3的课程
        List<User> lists = SDFrame.read(studentList)
                 // 根据用户姓名进行分组,组内根据课程成绩降序排序
                .window(Window.groupBy(User::getName).sortDesc(User::getScore))
                 // 生成组内的排名列
                .overDenseRank()
                // 保留排名小于等于3的 成绩
                .whereLe(FI2::getC2, 3)
                .map(FI2::getC1)
                .toLists();

        for (User e : lists) {
            System.out.println(e);
        }
    }

最后输出的结果如下:

代码语言:javascript
代码运行次数:0
运行
复制
User(id=2, name=李四, subject=英语, score=90)
User(id=2, name=李四, subject=科学, score=80)
User(id=2, name=李四, subject=物理, score=70)
User(id=1, name=张三, subject=英语, score=100)
User(id=1, name=张三, subject=语文, score=90)
User(id=1, name=张三, subject=数学, score=88)
User(id=3, name=王五, subject=地理, score=93)
User(id=3, name=王五, subject=数学, score=89)
User(id=3, name=王五, subject=物理, score=69)

接下来分析下代码执行过程:

首选执行了window开窗overDenseRank窗口计算函数之后,JDFrame 会把数据处理成如下图,会先分组排序,然后组内根据成绩进行排名,然后将生成的排名字段值列放到 FI2 类的 c2 字段进行接收,c1 字段存放每个学生,这样就得到了排名。 然后再执行 .whereLe(FI2::getC2, 3) 过滤 c2 字段值为小于等于 3 的就是每个学生的排名前 3 的成绩了

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

最后

JDFrame 熟练后能在一定程度上提升我们处理数据的效率和可读性, 但是需要使用者有矩阵计算的思想, 以及需要掌握 JDFrame 每个方法处理后的数据是怎么样的,是怎么生成和接收数据处理后的结果,这样你才能进一步的进行数据处理。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-07-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码哥跳动 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你好,见字如面。我是《Redis 高手心法》作者码哥,腾讯云架构师同盟深圳区理事会成员、InfoQ 签约作者,是一个手持菜刀砍电线,一路火花带闪电的靓仔。
    • 环境准备
    • (where 过滤)SQL 式的数据筛选
    • (分组聚合)学校成绩排名统计
    • (爆炸函数)爱好统计
    • (窗口函数)TopN 问题
    • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档