在 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}]
===== 示例结束 ======