Java Stream API:让业务数据处理更优雅

在 Java 业务开发中,我们经常需要对集合数据进行**筛选(filter)、转换(map)、聚合(collect)**等操作。比如从一批结果中过滤出符合条件的记录,就像这样:

假数据

java 复制代码
// 原始数据集合
List<Entity> entityList = Arrays.asList(
    new Entity(1L, "数据1", 200L),
    new Entity(2L, "数据2", 200L),
    new Entity(3L, "数据3", 300L),
    new Entity(4L, "数据4", 200L)
);
Long targetId = 200L; // 目标关联ID
id name associateId
1L 数据 1 200L
2L 数据 2 200L
3L 数据 3 300L
4L 数据 4 200L

操作代码

java 复制代码
List<Entity> resultItemList = entityList.stream()
    .filter(dataItem -> targetId.equals(dataItem.getAssociateId()))
    .collect(Collectors.toList());

结果 :包含 associateId 为 200L 的 3 条数据(ID 为 1、2、4)
filter:满足条件的对象才会被保留;
dataItem :代表 entityList 集合中的每一个 Entity 对象;
collect(Collectors.toList()) :将筛选后的结果收集到新的List中;

这种基于 Stream API 的处理方式以声明式的语法,让代码更简洁、意图更清晰。下面我们结合实际业务场景,探讨 Stream API 的更多实用写法。

提取关键信息:从对象集合到字段集合

在处理用户数据时,经常需要提取某个字段形成新集合。例如从用户列表中提取所有 ID,用于后续的批量查询:

假数据

java 复制代码
List<User> userList = Arrays.asList(
    new User(101L, "张三", 25),
    new User(102L, "李四", 30),
    new User(103L, "王五", 28)
);
id name age
101L 张三 25
102L 李四 30
103L 王五 28

操作代码

java 复制代码
List<Long> userIdList = userList.stream()
    .map(User::getId)
    .collect(Collectors.toList());

结果[101, 102, 103]

这种操作在权限校验、关联查询等场景中极为常见。通过map方法可以轻松实现对象到字段的转换,避免了手动迭代的繁琐。

多条件筛选:精准定位目标数据

业务系统中,数据筛选往往需要满足多重条件。比如筛选 "已激活且余额大于 0 的 VIP 用户":

假数据

java 复制代码
List<User> userList = Arrays.asList(
    new User(101L, "张三", UserStatus.ACTIVATED, 500.0, true),
    new User(102L, "李四", UserStatus.LOCKED, 1000.0, true),
    new User(103L, "王五", UserStatus.ACTIVATED, -50.0, true),
    new User(104L, "赵六", UserStatus.ACTIVATED, 800.0, false)
);
id name status balance isVip
101L 张三 ACTIVATED 500.0 true
102L 李四 LOCKED 1000.0 true
103L 王五 ACTIVATED -50.0 true
104L 赵六 ACTIVATED 800.0 false

操作代码

java 复制代码
List<User> qualifiedUsers = userList.stream()
    .filter(user -> UserStatus.ACTIVATED.equals(user.getStatus()))
    .filter(user -> user.getBalance() > 0)
    .filter(user -> user.isVip())
    .collect(Collectors.toList());

结果:仅包含用户 "张三"(ID=101)

通过链式调用filter方法逐步缩小范围,最终仅保留同时满足以下条件的用户,相比嵌套的 if 语句,这种写法更易于理解和维护。

分组聚合:数据归类的高效方式

数据分组是统计分析的基础操作。按部门 ID 分组统计员工列表:

假数据

java 复制代码
List<User> userList = Arrays.asList(
    new User(101L, "张三", 1L), // 部门ID=1
    new User(102L, "李四", 2L), // 部门ID=2
    new User(103L, "王五", 1L), // 部门ID=1
    new User(104L, "赵六", 3L)  // 部门ID=3
);
id name deptId
101L 张三 1L
102L 李四 2L
103L 王五 1L
104L 赵六 3L

操作代码

java 复制代码
Map<Long, List<User>> deptUserMap = userList.stream()
    .collect(Collectors.groupingBy(User::getDeptId));

结果

复制代码
{
    1L: [
        User{id=101, name='张三', deptId=1},
        User{id=103, name='王五', deptId=1}
    ],
    2L: [
        User{id=102, name='李四', deptId=2}
    ],
    3L: [
        User{id=104, name='赵六', deptId=3}
    ]
}

如果需要进一步统计数量,比如统计每个订单状态的数量:

假数据

java 复制代码
List<Order> orderList = Arrays.asList(
    new Order(1L, OrderStatus.PAID),
    new Order(2L, OrderStatus.PAID),
    new Order(3L, OrderStatus.CANCELLED),
    new Order(4L, OrderStatus.PENDING)
);
id status
1L PAID
2L PAID
3L CANCELLED
4L PENDING

操作代码

java 复制代码
Map<OrderStatus, Long> statusCountMap = orderList.stream()
    .collect(Collectors.groupingBy(Order::getStatus, Collectors.counting()));

结果

复制代码
{
    PAID: 2,
    CANCELLED: 1,
    PENDING: 1
}

这种分组统计在生成报表、数据看板等场景中应用广泛。

映射转换:构建快速查询结构

将列表转换为 Map 是提升查询效率的常用手段。例如将用户列表转为 "用户 ID - 用户对象" 的映射:

假数据

java 复制代码
List<User> userList = Arrays.asList(
    new User(101L, "张三"),
    new User(102L, "李四"),
    new User(101L, "张三(重复ID)") // 模拟重复ID
);
id name
101L 张三
102L 李四
101L 张三(重复 ID)

操作代码

java 复制代码
Map<Long, User> userMap = userList.stream()
    .collect(Collectors.toMap(
        User::getId, 
        Function.identity(),
        (existing, replacement) -> replacement // 重复时保留新值
    ));

结果

复制代码
{
    101L: User(101, "张三(重复ID)"),
    102L: User(102, "李四")
}

Function.identity():静态方法,表示将流中的元素(User对象)本身作为Map的值。

通过指定 key 生成规则和冲突处理策略,可以轻松构建高效的查询结构,特别适合需要频繁根据 ID 查询对象的场景。

排序与限制:获取 topN 数据

业务中常需要获取排名靠前的数据,如销量最高的前 10 个商品:

假数据

java 复制代码
List<Product> productList = Arrays.asList(
    new Product(1L, "手机", 1500L),
    new Product(2L, "电脑", 800L),
    new Product(3L, "平板", 2000L),
    new Product(4L, "手表", 1200L)
);
id name sales
1L 手机 1500L
2L 电脑 800L
3L 平板 2000L
4L 手表 1200L

操作代码

java 复制代码
List<Product> top10Products = productList.stream()
    .sorted(Comparator.comparingLong(Product::getSales).reversed())
    .limit(2) // 简化示例,取前2
    .collect(Collectors.toList());

结果:按销量倒序排列的前 2 条数据(平板、手机)

sorted(Comparator.comparingLong(Product::getSales).reversed()) 通过Comparator.comparingLong方法创建比较器,按Product对象的getSales()方法返回值(long类型)进行排序。
reversed()表示降序排列(从高到低)。

sorted方法支持自定义排序规则,配合limit可以快速实现 topN 查询,避免了手动排序的复杂逻辑。

数值计算:聚合操作的便捷实现

对数值型字段进行聚合计算是业务统计的常见需求。例如计算所有订单的总金额:

假数据

java 复制代码
List<Order> orderList = Arrays.asList(
    new Order(1L, new BigDecimal("199.99")),
    new Order(2L, new BigDecimal("299.50")),
    new Order(3L, new BigDecimal("500.00"))
);
id amount
1L 199.99
2L 299.50
3L 500.00

操作代码

java 复制代码
BigDecimal totalAmount = orderList.stream()
    .map(Order::getAmount)
    .reduce(BigDecimal.ZERO, BigDecimal::add);

结果999.49
.reduce(BigDecimal.ZERO, BigDecimal::add):对流中的BigDecimal值进行累加求和。BigDecimal.ZERO是初始值,BigDecimal::add是累加操作【如果orderList为空,reduce会直接返回初始值BigDecimal.ZERO,不会抛出异常。】;
reduce方法提供了灵活的聚合能力,除了求和,还可以实现求最大值、最小值等操作,满足多样化的统计需求。

类型转换:DTO 与 VO 的优雅映射

在分层架构中,经常需要在 DTO 和 VO 之间进行转换。例如将符合条件的订单 DTO 转换为 VO:

假数据

java 复制代码
List<OrderDTO> orderDTOList = Arrays.asList(
    new OrderDTO("ORD001", "张三", new BigDecimal("800")),
    new OrderDTO("ORD002", "李四", new BigDecimal("1200")),
    new OrderDTO("ORD003", "王五", new BigDecimal("1500"))
);
orderNo buyerName amount
ORD001 张三 800
ORD002 李四 1200
ORD003 王五 1500

操作代码

java 复制代码
List<OrderVO> orderVOList = orderDTOList.stream()
    .filter(dto -> dto.getAmount().compareTo(new BigDecimal("1000")) > 0)
    .map(dto -> {
        OrderVO vo = new OrderVO();
        vo.setOrderNo(dto.getOrderNo());
        vo.setUserName(dto.getBuyerName());
        vo.setTotalAmount(dto.getAmount());
        return vo;
    })
    .collect(Collectors.toList());

结果:包含 2 条数据(ORD002、ORD003),已转换为 OrderVO 类型

结合过滤和映射操作,可以在转换过程中同时完成数据清洗,使代码更加紧凑高效。

Stream API 的这些操作并非孤立存在,实际业务中常常需要组合使用。例如先过滤不符合条件的数据,再进行分组统计;或者先排序,再取前 N 条进行类型转换。这种流水线式的处理方式,不仅使代码结构清晰,更能直观地体现业务逻辑,大大提升了开发效率和代码可维护性。

完整代码

我用夸克网盘给你分享了「stream流配套代码」,链接:https://pan.quark.cn/s/9b16328cef08

下面这个是主程序,其余实体类文件在网盘里

java 复制代码
package stream;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class StreamDemoAll {
    public static void main(String[] args) {
        System.out.println("===== Java Stream API 示例 ======");

        // 1. 筛选操作 - 从Entity集合中筛选出associateId为200L的数据
        System.out.println("\n1. 筛选操作:");
        List<Entity> entityList = Arrays.asList(
            new Entity(1L, "数据1", 200L),
            new Entity(2L, "数据2", 200L),
            new Entity(3L, "数据3", 300L),
            new Entity(4L, "数据4", 200L)
        );
        Long targetId = 200L;
        List<Entity> resultItemList = entityList.stream()
            .filter(dataItem -> targetId.equals(dataItem.getAssociateId()))
            .collect(Collectors.toList());
        System.out.println("筛选结果: " + resultItemList);
        resultItemList.forEach(item -> System.out.println("ID: " + item.getId() + ", 名称: " + item.getName() + ", 关联ID: " + item.getAssociateId()));

        // 2. 提取关键信息 - 从User集合中提取所有ID
        System.out.println("\n2. 提取关键信息:");
        List<User> userList1 = Arrays.asList(
            new User(101L, "张三", 25),
            new User(102L, "李四", 30),
            new User(103L, "王五", 28)
        );
        List<Long> userIdList = userList1.stream()
            .map(User::getId)
            .collect(Collectors.toList());
        System.out.println("用户ID列表: " + userIdList);

        // 3. 多条件筛选 - 筛选已激活且余额大于0的VIP用户
        System.out.println("\n3. 多条件筛选:");
        List<User> userList2 = Arrays.asList(
            new User(101L, "张三", UserStatus.ACTIVATED, 500.0, true),
            new User(102L, "李四", UserStatus.LOCKED, 1000.0, true),
            new User(103L, "王五", UserStatus.ACTIVATED, -50.0, true),
            new User(104L, "赵六", UserStatus.ACTIVATED, 800.0, false)
        );
        List<User> qualifiedUsers = userList2.stream()
            .filter(user -> UserStatus.ACTIVATED.equals(user.getStatus()))
            .filter(user -> user.getBalance() > 0)
            .filter(user -> user.getVip())
            .collect(Collectors.toList());
        System.out.println("符合条件的用户: " + qualifiedUsers);

        // 4. 分组聚合 - 按部门ID分组统计员工列表
        System.out.println("\n4. 分组聚合:");
        List<User> userList3 = Arrays.asList(
            new User(101L, "张三", 1L),
            new User(102L, "李四", 2L),
            new User(103L, "王五", 1L),
            new User(104L, "赵六", 3L)
        );
        Map<Long, List<User>> deptUserMap = userList3.stream()
            .collect(Collectors.groupingBy(User::getDeptId));
        System.out.println("按部门分组的用户: ");
        deptUserMap.forEach((deptId, users) -> {
            System.out.println("部门 " + deptId + ": " + users);
        });

        // 5. 统计数量 - 统计每个订单状态的数量
        System.out.println("\n5. 统计数量:");
        List<Order> orderList1 = Arrays.asList(
            new Order(1L, OrderStatus.PAID),
            new Order(2L, OrderStatus.PAID),
            new Order(3L, OrderStatus.CANCELLED),
            new Order(4L, OrderStatus.PENDING)
        );
        Map<OrderStatus, Long> statusCountMap = orderList1.stream()
            .collect(Collectors.groupingBy(Order::getStatus, Collectors.counting()));
        System.out.println("订单状态统计: ");
        statusCountMap.forEach((status, count) -> {
            System.out.println(status + ": " + count);
        });

        // 6. 映射转换 - 将用户列表转为ID-用户对象映射
        System.out.println("\n6. 映射转换:");
        List<User> userList4 = Arrays.asList(
            new User(101L, "张三"),
            new User(102L, "李四"),
            new User(101L, "张三(重复ID)") // 模拟重复ID
        );
        Map<Long, User> userMap = userList4.stream()
            .collect(Collectors.toMap(
                User::getId,
                Function.identity(),
                (existing, replacement) -> replacement // 重复时保留新值
            ));
        System.out.println("用户ID-对象映射: ");
        userMap.forEach((id, user) -> {
            System.out.println(id + ": " + user);
        });

        // 7. 排序与限制 - 获取销量最高的前2个商品
        System.out.println("\n7. 排序与限制:");
        List<Product> productList = Arrays.asList(
            new Product(1L, "手机", 1500L),
            new Product(2L, "电脑", 800L),
            new Product(3L, "平板", 2000L),
            new Product(4L, "手表", 1200L)
        );
        List<Product> topProducts = productList.stream()
            .sorted(Comparator.comparingLong(Product::getSales).reversed())
            .limit(2)
            .collect(Collectors.toList());
        System.out.println("销量最高的前2个商品: " + topProducts);

        // 8. 数值计算 - 计算所有订单的总金额
        System.out.println("\n8. 数值计算:");
        List<Order> orderList2 = Arrays.asList(
            new Order(1L, new BigDecimal("199.99")),
            new Order(2L, new BigDecimal("299.50")),
            new Order(3L, new BigDecimal("500.00"))
        );
        BigDecimal totalAmount = orderList2.stream()
            .map(Order::getAmount)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        System.out.println("订单总金额: " + totalAmount);

        // 9. 类型转换 - DTO与VO的优雅映射
        System.out.println("\n9. 类型转换:");
        List<OrderDTO> orderDTOList = Arrays.asList(
            new OrderDTO("ORD001", "张三", new BigDecimal("800")),
            new OrderDTO("ORD002", "李四", new BigDecimal("1200")),
            new OrderDTO("ORD003", "王五", new BigDecimal("1500"))
        );
        List<OrderVO> orderVOList = orderDTOList.stream()
            .filter(dto -> dto.getAmount().compareTo(new BigDecimal("1000")) > 0)
            .map(dto -> {
                OrderVO vo = new OrderVO();
                vo.setOrderNo(dto.getOrderNo());
                vo.setUserName(dto.getBuyerName());
                vo.setTotalAmount(dto.getAmount());
                return vo;
            })
            .collect(Collectors.toList());
        System.out.println("转换后的订单VO列表: " + orderVOList);

        System.out.println("\n===== 示例结束 ======");
    }
}
复制代码
===== Java Stream API 示例 ======

1. 筛选操作:
筛选结果: [stream.Entity@270421f5, stream.Entity@52d455b8, stream.Entity@4f4a7090]
ID: 1, 名称: 数据1, 关联ID: 200
ID: 2, 名称: 数据2, 关联ID: 200
ID: 4, 名称: 数据4, 关联ID: 200

2. 提取关键信息:
用户ID列表: [101, 102, 103]

3. 多条件筛选:
符合条件的用户: [User{id=101, name='张三', age=null, status=ACTIVATED, balance=500.0, isVip=true, deptId=null}]

4. 分组聚合:
按部门分组的用户: 
部门 1: [User{id=101, name='张三', age=null, status=null, balance=null, isVip=null, deptId=1}, User{id=103, name='王五', age=null, status=null, balance=null, isVip=null, deptId=1}]
部门 2: [User{id=102, name='李四', age=null, status=null, balance=null, isVip=null, deptId=2}]
部门 3: [User{id=104, name='赵六', age=null, status=null, balance=null, isVip=null, deptId=3}]

5. 统计数量:
订单状态统计: 
PENDING: 1
PAID: 2
CANCELLED: 1

6. 映射转换:
用户ID-对象映射: 
101: User{id=101, name='张三(重复ID)', age=null, status=null, balance=null, isVip=null, deptId=null}
102: User{id=102, name='李四', age=null, status=null, balance=null, isVip=null, deptId=null}

7. 排序与限制:
销量最高的前2个商品: [Product{id=3, name='平板', sales=2000}, Product{id=1, name='手机', sales=1500}]

8. 数值计算:
订单总金额: 999.49

9. 类型转换:
转换后的订单VO列表: [OrderVO{orderNo='ORD002', userName='李四', totalAmount=1200}, OrderVO{orderNo='ORD003', userName='王五', totalAmount=1500}]

===== 示例结束 ======