Stream 代码越写越难看?JDFrame 让 Java 逻辑回归优雅

上周加完班打车回家,师傅问我:"这么晚才下班,程序员很辛苦吧?"

我说:"还好,就今天比较晚。"

师傅笑了笑没说话,但我心里苦啊。

事情是这样的。我们系统里有个报表模块,要从各种数据源捞数据,然后做转换、过滤、分组、汇总。光是DTO转VO,一天就得写七八次。

简单for循环

代码大概是这样:

java 复制代码
List<User> users = userService.findAll();
List<UserVO> result = new ArrayList<>();
for (User user : users) {
    if (user.isActive() && user.getAge() > 18) {
        UserVO vo = new UserVO();
        vo.setName(user.getName());
        vo.setAge(user.getAge());
        // 还有七八个字段要set...
        result.add(vo);
    }
}

代码其实没毛病,就是不太好看。

于是用到了 Java8 的 Stream。

Stream 写法

java 复制代码
List<UserVO> result = users.stream()
    .filter(user -> user.isActive() && user.getAge() > 18)
    .map(user -> {
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        return vo;
    })
    .collect(Collectors.toList());

看起来好多了,对吧?但这只是最简单的场景。

举例

假设有这样一个需求:

从员工列表里,按部门分组,计算每个部门的平均薪资,找出平均薪资大于5000的部门,按平均薪资从高到低排序,取前三名。

数据类长这样:

java 复制代码
@Data
@AllArgsConstructor
class Employee {
    String name;
    String dept;
    double salary;
}

Stream实现:

java 复制代码
List<Employee> employees = Arrays.asList(
    new Employee("张三", "技术部", 12000),
    new Employee("李四", "技术部", 9000),
    new Employee("王五", "市场部", 6000),
    new Employee("赵六", "市场部", 4000),
    new Employee("钱七", "人事部", 3000),
    new Employee("孙八", "人事部", 8000)
);

List<String> topDepts = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDept,
        Collectors.averagingDouble(Employee::getSalary)
    ))
    .entrySet().stream()
    .filter(e -> e.getValue() > 5000)
    .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
    .limit(3)
    .map(Map.Entry::getKey)
    .collect(Collectors.toList());

这里就有几个让人不舒服的地方:

  1. groupingBy,再对entrySet()流式处理,绕了一圈。
  2. 排序那段泛型推导,必须写Map.Entry.<String, Double>comparingByValue(),看着就累。
  3. 如果后续还要加条件(比如再关联另一张表),这个链会越来越臃肿。

这种代码在数据量不大、步骤少的时候没问题,可一旦要做转换、聚合等操作时,Stream 可读性会下降。


换JDFrame来写,画风突变

先加依赖(Maven):

xml 复制代码
<dependency>
    <groupId>io.github.davidfantasy</groupId>
    <artifactId>jdframe</artifactId>
    <version>最新版本</version>
</dependency>

然后用它重写上面的逻辑:

java 复制代码
DataFrame<Employee> df = JDFrame.create(employees);

List<String> topDepts = df.groupBy("dept")
        .avg("salary").as("avgSalary")
        .filter(row -> row.getDouble("avgSalary") > 5000)
        .sortDesc("avgSalary")
        .select("dept")
        .head(3)
        .toList(row -> row.getString("dept"));

是不是清晰的像写 SQL 一样?

  • groupBy("dept") → 按部门分组
  • avg("salary") → 计算薪资平均值
  • filter(...) → 过滤平均薪资>5000
  • sortDesc(...) → 降序排序
  • select("dept") → 只取部门列
  • head(3) → 取前三条

每一步都对应自然语言的一个动作,没有中间集合,没有泛型推导,没有流套流


JDFrame到底是什么?

简单说,JDFrame 是一个把集合数据当成表格来操作的工具。

对比 Stream:

  • Stream:一条一条遍历,一个个对象处理。
  • JDFrame :而 JDFrame 换了一种思路,它把你的 List<Employee> 看成一张表,适合多步骤数据清洗、报表统计、类SQL查询。

上手JDFrame,三步就够了

1. 创建DataFrame

最常见的是从List创建:

java 复制代码
DataFrame<Employee> df = JDFrame.create(employees);

也可以从CSV文件、数据库ResultSet、二维数组等创建。

2. 日常操作------像玩Excel一样玩数据

筛选行 (类似SQL的WHERE):

java 复制代码
df.filter(row -> row.getInt("age") > 30);
df.filterEq("dept", "技术部");   // 等于某个值
df.filterIn("status", Arrays.asList(1,2,3)); // in查询

选择列 (类似SQL的SELECT):

java 复制代码
df.select("name", "salary");
df.drop("age"); // 删除某列

新增/修改列

java 复制代码
df.withColumn("annualSalary", row -> row.getDouble("salary") * 12);

排序

java 复制代码
df.sortAsc("age");
df.sortDesc("salary");
df.sort("dept", true, "age", false); // 多列排序:dept升序,age降序

分组聚合------这是JDFrame最爽的地方:

java 复制代码
df.groupBy("dept")
  .count().as("count")
  .sum("salary").as("total")
  .avg("salary").as("avg")
  .max("age").as("maxAge")
  .toList();

所有聚合函数都可以链式调用,结果就是一张新的DataFrame,不用像Stream那样先groupingBy,再处理Entry

3. 两个表格之间的连接(Join)

这是Stream最难搞的场景,但JDFrame几行搞定:

java 复制代码
DataFrame<Employee> empDF = JDFrame.create(employees);
DataFrame<Dept> deptDF = JDFrame.create(depts);

DataFrame<?> joined = empDF.innerJoin(deptDF, "deptId", "id")
        .select("emp.name", "dept.deptName", "emp.salary");

innerJoinleftJoinrightJoinfullJoin都有,连接条件支持单字段或多字段组合。


哪些地方特别适合用JDFrame?

1. 报表导出/数据统计

比如从数据库查了几万条订单记录,要在内存中按地区、品类、时间段做多层聚合,最后生成Excel。

Stream写出来基本是个屎山,而JDFrame的链式分组聚合非常时候这种场景。

2. 数据清洗/ETL

从文件读入原始数据,需要去重、转换格式、填充默认值、过滤脏数据......JDFrame的withColumnfilterdropDuplicates一套下来,逻辑非常直观。

3. 接口数据组装

现在微服务流行,经常要从A服务查一批用户,从B服务查他们的订单,然后在内存里把两个列表关联起来,再按某种规则排序分页。

以前可能要自己维护Map<userId, List>,现在一个leftJoin搞定。


不是所有地方都适用

性能

JDFrame内部还是基于Stream+内存操作,大数据量(百万级以上)不如数据库直接聚合,适合中等规模数据。

学习成本

习惯了Stream的小伙伴刚接触列式思维,可能需要半天适应,不过一旦上手就回不去了。

生态

毕竟小众,和Spring、MyBatis没有官方集成,需要自己粘合。但这反而是优点------小巧,无侵入


写在最后

Stream 当然是个好东西,简单的过滤映射依然是它的主场。

但当我们遇到多步骤、表关联、类SQL聚合 的场景时,可以尝试下 JDFrame

从设计思想上看,Stream 是面向对象 + 函数式组合的思路,而 JDFrame 更偏向列式数据模型。

一个是操作对象流,一个是操作表结构。

JDFrame 的价值,不是取代 Stream,而是补充。

本文首发于公众号:程序员大华,专注前端、Java开发,AI应用和工具的分享。关注我,少走弯路,一起进步!

相关推荐
ray_liang1 小时前
用六边形架构与整洁架构对比是伪命题?
java·架构
哈密瓜的眉毛美2 小时前
零基础学Java|第五篇:进制转换与位运算、原码反码补码
后端
开心就好20252 小时前
免 Xcode 的 iOS 开发新选择?聊聊一款更轻量的 iOS 开发 IDE kxapp 快蝎
后端·ios
Java编程爱好者2 小时前
为什么国内大厂纷纷”弃坑”MySQL,转投PostgreSQL阵营?
后端
神奇小汤圆3 小时前
金三银四Java面试题及答案汇总(2026持续更新)
后端
Ray Liang3 小时前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
颜酱3 小时前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
神奇小汤圆3 小时前
加了 limit 1,查询竟然变慢了?
后端
Java水解3 小时前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端