作者:微信小助手
发布时间:2025-04-01T10:04:26
兄弟们,前几天在技术群里看到一个令人窒息的代码片段:
List
users = ...;
Map
List
> result = users.stream()
.filter(u -> u.getStatus() == UserStatus.ACTIVE)
.filter(u -> u.getAge() > 18)
.filter(u -> u.getOrders().size() > 0)
.flatMap(u -> u.getOrders().stream())
.collect(Collectors.groupingBy(Order::getUserId,
Collectors.mapping(Order::getId, Collectors.toList())));
这段代码乍一看很“流式”,但仔细分析就会发现:
连续三个filter像俄罗斯套娃
flatMap后的collect嵌套层数堪比俄罗斯方块
最终结果类型Map>与实际需求完全不符
这让我想起最近流行的一句话:“Stream用得好是瑞士军刀,用不好就是小李飞刀——刀刀见血,但扎的是自己。”
有些同学对链式调用有一种迷之执着,恨不得把所有操作塞进一条流里:
List
result =
list.stream()
.filter(...).map(...).flatMap(...).distinct()
.sorted(...).limit(...).skip(...).collect(...);
危害:超过5个操作的链式调用会让代码可读性直线下降,调试时根本分不清问题出在哪一环。
解药:
// 分段处理,用中间变量过渡
Stream<String> filtered = list.stream().filter(...);
Stream<String> mapped = filtered.map(...);
List<String> result = mapped.collect(...);
在某个电商项目里,我见过这样的代码:
String skuIds = order.getItems().stream()
.map(Item::getSkuId)
.collect(Collectors.joining(","));
问题:如果订单没有商品,会得到空字符串,而空字符串和null在业务逻辑中是不同的概念。
改进方案:
Optional
skuIdsOpt = order.getItems().stream()
.map(Item::getSkuId)
.collect(Collectors.joining(","))
.empty() ? Optional.empty() : Optional.of(skuIds);
某支付系统的关键路径上有这样的代码:
List
transactions = db.query(...).stream()
.filter(t -> t.getAmount() > 1000)
.collect(Collectors.toList());
隐患:当数据量达到百万级时,toList()会导致内存溢出。
优化方法:
// 使用并行流+分批处理
List
result =
new ArrayList<>();
db.query(...).stream()
.parallel()
.filter(t -> t.getAmount() > 1000)
.forEachOrdered(result::add);
在某个金融计算场景中:
double sum = numbers.stream()
.boxed()
.mapToDouble(Number::doubleValue)
.sum();
误区:boxed()会将原始类型转换为包装类型,导致性能下降。
正确姿势:
double sum = numbers.stream()
.mapToDouble(Number::doubleValue)
.sum();
反模式:
List
products = productList.stream()
.filter(p -> p.getPrice() > 100)
.filter(p -> p.getStock() > 0)
.collect(Collectors.toList());
重构方案:
// 定义独立的Predicate
Predicate
isExpensive = p -> p.getPrice() >
100;
Predicate
isInStock = p -> p.getStock() >
0;
List
products = productList.stream()
.filter(isExpensive.and(isInStock))
.collect(Collectors.toList());
在监控日志场景中:
List
orders = orderList.stream()
.peek(o -> log.info("Processing order: {}", o.getId()))
.filter(o -> o.getStatus() == COMPLETED)
.collect(Collectors.toList());
注意事项:
peek()只能用于调试或副作用操作
避免在peek中修改流元素
并行流中慎用peek()
在权限系统中,我们需要将用户角色收集为枚举集合:
public class RoleCollectors {
public static Collector
> rolesToSet() {
return Collector.of(
HashSet::new,
(set, user) -> set.add(user.getRole()),
(left, right) -> { left.addAll(right); return left; },
Collector.Characteristics.UNORDERED
);
}
}
// 使用示例
Set
roles = users.stream()
.collect(RoleCollectors.rolesToSet());
在商品推荐场景中:
List
recommendations = productList.stream()
.filter(p -> p.getCategory().equals("electronics"))
.findFirst()
.map(Collections::singletonList)
.orElse(Collections.emptyList());
更优雅的方式:
List
recommendations = productList.stream()
.filter(p -> p.getCategory().equals("electronics"))
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> list.isEmpty() ? Collections.singletonList(defaultProduct) : list
));
在用户服务中:
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException(userId));
return user.getEmail();
Stream优化版:
return userRepository.findById(userId)
.map(User::getEmail)
.orElseThrow(() -> new UserNotFoundException(userId));
在物流系统中,订单处理分为三个阶段:
Stream
orderStream = orderRepository.findAll().stream();
// 阶段1:过滤有效订单
Stream
validOrders = orderStream
.filter(Order::isValid)
.peek(o -> auditLogService.logValidation(o));
// 阶段2:计算运费
Map
shippingFees = validOrders
.collect(Collectors.toMap(
Order::getId,
this::calculateShippingFee
));
// 阶段3:更新订单状态
validOrders.forEach(o -> orderService.updateStatus(o, SHIPPING));
在统计用户画像时:
UserProfile profile = users.stream()
.collect(Collectors.teeing(
Collectors.averagingInt(User::getAge),
Collectors.summingDouble(User::getSpending),
(avgAge, totalSpending) -> new UserProfile(avgAge, totalSpending)
));
在文件处理场景中:
List
lines = Files.readAllLines(Paths.get(
"data.txt"));
lines.parallelStream()
.map(this::processLine)
.forEachOrdered(System.out::println);
关键点:
顺序敏感操作必须使用forEachOrdered()
共享资源需要线程安全处理
避免过度使用并行流
在处理海量日志时:
public class LogSpliterator implements Spliterator<LogEntry> {
privatefinal List
logs;
privateint currentIndex;
public LogSpliterator(List
logs)
{
this.logs = logs;
this.currentIndex = 0;
}
@Override
public boolean tryAdvance(Consumersuper LogEntry> action) {
if (currentIndex < logs.size()) {
action.accept(logs.get(currentIndex++));
returntrue;
}
returnfalse;
}
@Override
public Spliterator
trySplit() {
int currentSize = logs.size() - currentIndex;
if (currentSize < 1000) returnnull; // 阈值设定
int splitPos = currentIndex + currentSize / 2;
Spliterator
split =
new LogSpliterator(logs.subList(currentIndex, splitPos));
currentIndex = splitPos;
return split;
}
@Override
public long estimateSize() {
return logs.size() - currentIndex;
}
@Override
public int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
}
// 使用示例
StreamSupport.stream(new LogSpliterator(logs), true)
.parallel()
.forEach(this::processLog);
可读性检查
每个流操作是否有明确的业务含义
链式调用是否超过5层
是否使用了有意义的中间变量
性能检查
是否在不必要的情况下使用了boxed()
并行流是否适用于当前场景
收集操作是否考虑了数据规模
异常处理
是否处理了空流情况
是否合理使用了Optional
错误处理是否符合业务需求
可维护性
是否将复杂逻辑拆分为独立的方法
是否使用了自定义Collectors
是否考虑了未来的扩展需求
最后分享一个真实案例:某电商平台通过重构Stream代码,使核心订单处理流程的代码量减少40%,同时性能提升30%。他们的秘诀在于:
将流操作与业务逻辑解耦
建立Stream最佳实践库
使用IDE插件(如StreamRefactor)进行代码检查
记住:Stream不是代码竞赛的工具,而是提升代码质量的手段。当你开始思考如何让Stream代码更易读、易维护、易扩展时,你就真正掌握了它的精髓。