Java Stream API:让业务数据处理更优雅
在 Java 业务开发中,我们经常需要对集合数据进行**筛选(filter)、转换(map)、聚合(collect)**等操作。比如从一批结果中过滤出符合条件的记录,就像这样:
假数据:
// 原始数据集合
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 |
操作代码:
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,用于后续的批量查询:
假数据:
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 |
操作代码:
List<Long> userIdList = userList.stream().map(User::getId).collect(Collectors.toList());
结果:[101, 102, 103]
这种操作在权限校验、关联查询等场景中极为常见。通过map
方法可以轻松实现对象到字段的转换,避免了手动迭代的繁琐。
多条件筛选:精准定位目标数据
业务系统中,数据筛选往往需要满足多重条件。比如筛选 “已激活且余额大于 0 的 VIP 用户”:
假数据:
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 |
操作代码:
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 分组统计员工列表:
假数据:
List<User> userList = Arrays.asList(new User(101L, "张三", 1L), // 部门ID=1new User(102L, "李四", 2L), // 部门ID=2new User(103L, "王五", 1L), // 部门ID=1new User(104L, "赵六", 3L) // 部门ID=3
);
id | name | deptId |
---|---|---|
101L | 张三 | 1L |
102L | 李四 | 2L |
103L | 王五 | 1L |
104L | 赵六 | 3L |
操作代码:
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}]
}
如果需要进一步统计数量,比如统计每个订单状态的数量:
假数据:
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 |
操作代码:
Map<OrderStatus, Long> statusCountMap = orderList.stream().collect(Collectors.groupingBy(Order::getStatus, Collectors.counting()));
结果:
{PAID: 2,CANCELLED: 1,PENDING: 1
}
这种分组统计在生成报表、数据看板等场景中应用广泛。
映射转换:构建快速查询结构
将列表转换为 Map 是提升查询效率的常用手段。例如将用户列表转为 “用户 ID - 用户对象” 的映射:
假数据:
List<User> userList = Arrays.asList(new User(101L, "张三"),new User(102L, "李四"),new User(101L, "张三(重复ID)") // 模拟重复ID
);
id | name |
---|---|
101L | 张三 |
102L | 李四 |
101L | 张三(重复 ID) |
操作代码:
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 个商品:
假数据:
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 |
操作代码:
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 查询,避免了手动排序的复杂逻辑。
数值计算:聚合操作的便捷实现
对数值型字段进行聚合计算是业务统计的常见需求。例如计算所有订单的总金额:
假数据:
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 |
操作代码:
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:
假数据:
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 |
操作代码:
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
下面这个是主程序,其余实体类文件在网盘里
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集合中提取所有IDSystem.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: 2002. 提取关键信息:
用户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: 16. 映射转换:
用户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.499. 类型转换:
转换后的订单VO列表: [OrderVO{orderNo='ORD002', userName='李四', totalAmount=1200}, OrderVO{orderNo='ORD003', userName='王五', totalAmount=1500}]===== 示例结束 ======