咱做Java开发的,尤其是老程序员,估计都有过被集合处理“折磨”的经历——以前遍历个列表、过滤点数据、排个序,动辄写三四层循环,代码又长又丑,维护起来跟拆盲盒似的,不知道哪行藏着坑。直到Java 8推出了Lambda表达式和Stream API,才算给咱解放了双手。这俩玩意儿不是花架子,是真能落地省事儿的工具,今天就用大白话唠明白它们的作用,再说说项目里咋用它们简化集合处理,新手也能直接抄作业。
先声明下,咱不整那些晦涩的源码解析,全是项目里天天能用着的干货。毕竟咱开发的核心是搞定需求,不是背理论,能少写一行代码、少踩一个坑,比啥都强。
先搞懂:Lambda表达式到底是干啥的?
Lambda表达式说白了,就是给匿名内部类做了个“瘦身”。以前写匿名内部类,比如给集合排序、写线程任务,得把类名、方法名、参数列表全写一遍,冗余得不行。就像你去买奶茶,明明只要说“一杯珍珠奶茶少糖”,非要从头报一遍“我要购买一杯奶茶,品类是珍珠奶茶,甜度是少糖,温度是常温……”,纯属浪费时间。
Lambda的核心作用,就是用极简的语法表示一个“函数”,不用定义类和方法,直接把逻辑传给需要的地方。比如以前给List排序,得写个Comparator匿名内部类,好几行代码;用Lambda,一行搞定,清爽得很。而且它还能配合后面要说的Stream API,让集合处理的代码更简洁,可读性也更强——不是说代码越短越好,是越直观越好,别人接手你的代码,扫一眼就知道你要干啥,这才是精髓。
举个项目里常见的例子:给用户列表按年龄排序。以前的写法,得new一个Comparator,重写compare方法,写个三四行;用Lambda,直接(list) -> list.sort((u1, u2) -> u1.getAge() – u2.getAge()),一眼就能看出来是按年龄升序排序,不用在一堆冗余代码里找核心逻辑。
再明白:Stream API 能解决啥问题?
有了Lambda表达式打底,Stream API就像是给集合处理开了挂。它的核心作用,就是把集合操作“流水线化”——你可以把集合看成一个数据流,然后对这个流做过滤、映射、排序、统计这些操作,就像工厂里的生产线,原料(集合数据)进去,经过一系列加工(Stream操作),最后出来你想要的结果。
以前处理集合,得手动遍历、手动判断、手动累加,不仅代码长,还容易出错。比如想从订单列表里找出金额大于1000的订单,再提取出用户ID,最后去重,以前得写个循环,里面嵌套if判断,再用个Set去重,好几段代码;用Stream API,一串链式调用就能搞定,中间不用手动维护临时变量,也不用怕循环条件写错。
更爽的是,Stream API支持并行处理。以前处理海量数据,想并行遍历还得自己写线程池,处理线程安全问题,头都大了;Stream的parallelStream()方法,直接帮你实现并行处理,底层用的是Fork/Join框架,不用你管线程的事儿,直接调用就行,效率还高。不过并行处理也不是万能的,后面咱会说避坑点。
项目实战:用Lambda+Stream简化集合处理(这才是重点)
光说不练假把式,项目里最常见的集合处理场景,比如过滤、映射、排序、统计、分组,咱用Lambda+Stream一个个掰扯,每个场景都给以前的写法和现在的写法对比,高下立判。
场景1:过滤数据(最常用,没有之一)
需求:从用户列表里,过滤出年龄大于18岁、性别为男的用户,返回用户姓名列表。以前的写法,得new一个List当结果集,然后遍历用户列表,里面嵌套两个if判断,符合条件的就add到结果集里,代码得写七八行,还得维护临时List。
用Lambda+Stream的写法,一行链式调用搞定:List<String> maleAdultNames = userList.stream() .filter(user -> user.getAge() > 18 && “男”.equals(user.getGender())) .map(User::getName) .collect(Collectors.toList());
解释下,stream()获取流,filter()里用Lambda写过滤条件,map()提取姓名,最后collect()把结果收集成List。不用手动遍历,不用维护临时变量,别人一看就知道你要干啥,后期改条件也方便,比如把年龄改成20岁,直接改filter里的数值就行。
场景2:数据映射(提取/转换数据)
需求:从订单列表里,提取出所有订单的用户ID,并且去重。以前的写法,得new一个Set,遍历订单列表,一个个getUserId(),然后add到Set里,去重还得依赖Set的特性,代码又长又啰嗦。
用Lambda+Stream:Set<Long> userIds = orderList.stream() .map(Order::getUserId) .distinct() .collect(Collectors.toSet());
map()就是映射,把Order对象转换成UserId,distinct()自动去重,最后收集成Set。这里的Order::getUserId是方法引用,比写lambda表达式(order -> order.getUserId())更简洁,前提是方法匹配,项目里尽量用方法引用,代码更干净。
场景3:排序(不用再写Comparator匿名内部类)
需求:给商品列表按价格降序排序,价格相同的按销量升序排序。以前的写法,得new一个Comparator,重写compare方法,里面写两层判断,代码得五六行,还容易把升序降序搞反(比如把减号写反)。
用Lambda+Stream:List<Product> sortedProducts = productList.stream() .sorted((p1, p2) -> { if (p2.getPrice().compareTo(p1.getPrice()) != 0) { return p2.getPrice().compareTo(p1.getPrice()); } else { return p1.getSales().compareTo(p2.getSales()); } }) .collect(Collectors.toList());
要是排序逻辑简单,比如只按价格升序,更简洁:sorted(Comparator.comparing(Product::getPrice))。不用再写匿名内部类的一堆冗余代码,排序逻辑直接写在Lambda里,清晰明了,也不容易搞反顺序。
场景4:统计分析(不用手动累加计算)
需求:统计订单列表里,金额大于500的订单总金额、平均金额、最大金额。以前的写法,得new三个变量(总金额、平均金额、最大金额),遍历订单列表,一个个判断金额,然后手动累加、比较,代码又长又容易算错,比如忘记初始化总金额为0。
用Lambda+Stream:DoubleSummaryStatistics stats = orderList.stream() .filter(order -> order.getAmount() > 500) .mapToDouble(Order::getAmount) .summaryStatistics();
然后直接获取统计结果:double total = stats.getSum(); // 总金额 double avg = stats.getAverage(); // 平均金额 double max = stats.getMax(); // 最大金额
一行代码搞定统计,不用手动计算,也不用怕算错,Stream帮你把所有统计逻辑都封装好了,直接拿结果用就行,项目里做报表统计、数据汇总的时候,这玩意儿简直是神器。
场景5:分组聚合(按条件分组数据)
需求:把用户列表按性别分组,key是性别(男/女),value是该性别的用户列表。以前的写法,得new一个Map<String, List<User>>,遍历用户列表,判断性别,然后把用户add到对应的List里,代码得写十来行,还得判断map里有没有对应的key,没有就new一个List,麻烦得很。
用Lambda+Stream:Map<String, List<User>> userGroupByGender = userList.stream() .collect(Collectors.groupingBy(User::getGender));
一行代码搞定分组,Collectors.groupingBy()直接按指定字段分组,自动帮你处理key不存在的情况,不用手动判断和new List。要是想按多个条件分组,比如先按性别分组,再按年龄分组,也能通过嵌套groupingBy实现,比以前的写法简洁太多。
避坑提醒:这3个坑千万别踩(血的教训)
Lambda+Stream虽然好用,但也不是万能的,项目里用的时候要是不注意,很容易踩坑,咱把最容易踩的3个坑列出来,大家避开就行。
第一个坑:Stream流只能遍历一次。别想着把一个Stream流保存起来,第一次遍历完(比如collect()之后),再想遍历第二次,会直接报异常。解决办法也简单,要么重新获取流(比如再调用一次list.stream()),要么把流处理的结果保存到集合里,后续从集合里获取数据。
第二个坑:并行流(parallelStream)不是越用越好。并行流适合处理海量数据,比如几十万、几百万条数据,能提高效率;但要是数据量小(比如几百条),用并行流反而会因为线程切换开销,导致效率更低,还可能出现线程安全问题(比如用了非线程安全的集合收集结果)。数据量小的时候,老老实实用普通流(stream())就行。
第三个坑:Lambda里别修改外部变量。虽然Java 8允许Lambda里引用外部变量,但这个变量必须是final或者 effectively final( effectively final就是虽然没写final,但值没被修改过)。要是在Lambda里修改外部变量,不仅会报错,还会让代码可读性变差,后期排查问题也麻烦。想修改数据,尽量在流操作里处理,或者用流的结果去修改。
最后唠两句
Lambda表达式和Stream API,本质上是帮咱简化集合处理的工具,核心是让代码更简洁、更直观、更好维护。不是说用了它们就是高级开发,而是能用最简单的代码搞定需求,才是真的厉害。
项目里,只要涉及到集合的过滤、映射、排序、统计、分组这些操作,都可以用Lambda+Stream来写,比以前的循环写法省不少事,后期改需求也方便。新手刚开始用可能会觉得别扭,多写两个场景,很快就能上手,慢慢就会发现,再也回不去以前写循环的日子了。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。