尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java Stream 流高级用法,告别臃肿循环代码

Java Stream 流高级用法,告别臃肿循环代码 做后端开发集合处理几乎是每天都要面对的工作。最开始写业务遇到列表过滤、对象转换、分组统计第一反应就是写 for 循环里面嵌套 if 判断中间定义各种临时集合接收结果。业务逻辑复杂一点循环嵌套三四层代码几十行后续接手维护的时候读起来非常费劲一不小心还会引入 bug。很多人知道 Stream 可以简化集合遍历但大部分人仅仅停留在filter、map基础用法上遇到分组、归约、扁平化处理还是退回 for 循环。还有一部分同学盲目滥用 Stream线上出现 key 重复报错、并行流线程安全问题反而制造更多隐患。Stream 不是简单把 for 循环换个写法它是一套流式数据处理模型中间操作惰性执行一次遍历完成多步处理。下面结合真实业务场景从基础过渡到高级用法同时贴出对比代码方便直接复制到项目使用。传统循环臃肿写法模拟业务场景订单列表过滤有效订单提取订单编号按用户 ID 分组统计订单金额。先看普通 for 循环实现import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; class Order { private Long userId; private String orderNo; private BigDecimal amount; private Integer status; // 1有效 0无效 public Order(Long userId, String orderNo, BigDecimal amount, Integer status) { this.userId userId; this.orderNo orderNo; this.amount amount; this.status status; } // getter省略 public Long getUserId() {return userId;} public String getOrderNo() {return orderNo;} public BigDecimal getAmount() {return amount;} public Integer getStatus() {return status;} } public class ForDemo { public static void main(String[] args) { ListOrder orderList new ArrayList(); // 模拟数据 orderList.add(new Order(1L,O001,new BigDecimal(299),1)); orderList.add(new Order(1L,O002,new BigDecimal(599),1)); orderList.add(new Order(2L,O003,new BigDecimal(199),0)); MapLong,BigDecimal userTotalMap new HashMap(); ListString validOrderNos new ArrayList(); for(Order order : orderList){ // 过滤无效订单 if(order.getStatus()!1){ continue; } validOrderNos.add(order.getOrderNo()); // 分组累加金额 BigDecimal total userTotalMap.getOrDefault(order.getUserId(),BigDecimal.ZERO); userTotalMap.put(order.getUserId(),total.add(order.getAmount())); } System.out.println(有效订单号validOrderNos); System.out.println(用户总金额userTotalMap); } }上面代码逻辑没问题但可以看到需要手动维护两个容器写 if 判断变量多业务再复杂一点代码会持续膨胀。Stream 高级实战改造同样的业务逻辑使用 Stream 一次性完成过滤、提取、分组聚合。这里用到Collectors.groupingBy结合Collectors.reducing做金额归约求和这也是项目里高频的高级用法CSDN博...。import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; public class StreamAdvDemo { public static void main(String[] args) { ListOrder orderList new ArrayList(); orderList.add(new Order(1L,O001,new BigDecimal(299),1)); orderList.add(new Order(1L,O002,new BigDecimal(599),1)); orderList.add(new Order(2L,O003,new BigDecimal(199),0)); // 过滤有效订单 ListOrder validOrder orderList.stream() .filter(o - o.getStatus() 1) .collect(Collectors.toList()); // 获取订单编号列表 ListString orderNoList validOrder.stream() .map(Order::getOrderNo) .collect(Collectors.toList()); // 按用户ID分组求和订单金额 MapLong, BigDecimal userAmountSum validOrder.stream() .collect(Collectors.groupingBy( Order::getUserId, Collectors.reducing( BigDecimal.ZERO, Order::getAmount, BigDecimal::add ) )); System.out.println(orderNoList); System.out.println(userAmountSum); } }代码没有各种临时集合逻辑和业务语义一一对应读代码就是读需求。flatMap 扁平化处理实际开发经常遇到集合嵌套比如一个用户有多条收货地址ListUser里面每个 User 持有 ListAddress想要把全部地址提取成一个扁平列表。很多同学两层 for 循环嵌套flatMap 专门解决嵌套集合拆解问题CSDN博...。class User{ private ListString addressList; public User(ListString addressList){ this.addressList addressList; } public ListString getAddressList() {return addressList;} } // flatMap示例 ListUser userList Arrays.asList( new User(Arrays.asList(北京朝阳,北京海淀)), new User(Arrays.asList(上海浦东)) ); ListString allAddress userList.stream() .flatMap(user - user.getAddressList().stream()) .collect(Collectors.toList()); System.out.println(allAddress);partitioningBy 分区partitioningBy是特殊分组传入布尔条件自动分成 true、false 两组适合把集合切分成符合条件、不符合条件两部分比如区分大额订单和普通订单CSDN博...。MapBoolean, ListOrder partMap validOrder.stream() .collect(Collectors.partitioningBy(o - o.getAmount().compareTo(new BigDecimal(300))0)); // true是大于300订单false小于等于300 ListOrder bigOrder partMap.get(true); ListOrder smallOrder partMap.get(false);Stream 开发高频踩坑点写代码爽是一方面线上踩坑才是重点我项目里遇到过不少 Stream 引发的 bug这里整理几个高频问题。Collectors.toMap key 重复异常当集合存在重复 key直接Collectors.toMap()会抛出IllegalStateException。业务中一定要指定合并策略不要直接裸用 toMap。// 错误写法key重复直接报错 // .collect(Collectors.toMap(Order::getUserId,Order::getOrderNo)); // 正确重复key保留后者 .collect(Collectors.toMap(Order::getUserId,Order::getOrderNo,(oldVal,newVal)-newVal));流只能消费一次流执行 collect 之后就关闭不能二次复用重复调用会抛出异常。需要多次处理重新从原始集合生成 streamCSDN博...。//错误 StreamOrder stream orderList.stream().filter(o-o.getStatus()1); stream.collect(Collectors.toList()); //stream再次调用会报错不要盲目上 parallelStream 并行流很多人看到并行就觉得性能高直接所有地方加上 parallel。小数据量并行反而更慢并行流会使用 ForkJoinPool操作非线程安全集合会出现数据错乱。业务没有明确大数据量计算需求优先用普通 stream 串行流。不要在 forEach 里面做复杂业务聚合不少同学喜欢用 stream.forEach 循环里面 add 到外部 list这种写法失去流的意义并行场景还会并发异常尽量使用 collect 收集结果。Stream 和 for 循环怎么选不是所有场景都要用 Stream不要为了炫技强行改造。业务集合过滤、转换、分组、统计优先 Stream代码简洁可读性高集合元素很少小于千条级别简单循环操作普通 for 循环性能开销更小需要多层复杂业务逻辑、大量变量中间计算硬写 Stream 会导致 lambda 嵌套很深反而可读性变差老老实实写循环。写在最后Stream 的核心价值是把业务逻辑和循环控制解耦我们只需要关心要做什么而不是怎么循环遍历。很多人觉得 Stream 难懂本质是对 Collector 收集器体系不熟悉多把分组、归约、分区这些高级 API 用到实际项目慢慢就能摆脱到处写 for 循环的局面。但是也要记住Stream 不是银弹合理取舍写出可读性优先的业务代码。
返回列表