专栏名称: 芋道源码
纯 Java 源码分享公众号,目前有「Dubbo」「SpringCloud」「Java 并发」「RocketMQ」「Sharding-JDBC」「MyCAT」「Elastic-Job」「SkyWalking」「Spring」等等
51好读  ›  专栏  ›  芋道源码

用了Stream后,代码反而越写越丑?

芋道源码  · 公众号  · Java  · 2025-03-22 21:15

主要观点总结

本文介绍了在使用Java的Stream和Lambda表达式时的一些关键点,包括合理的换行、舍得拆分函数、合理的使用Optional、返回Stream还是返回List,以及少用或不用并行流等。同时,也强调了写代码不仅是技术问题,更是一个意识问题。

关键观点总结

关键观点1: 合理的换行

在Java的Stream和Lambda表达式中,要注意代码的排版和布局,使其更加清晰易读。

关键观点2: 舍得拆分函数

不要将过多的逻辑放在一个函数中,应该将其拆分成多个小函数,提高代码的可读性和可维护性。

关键观点3: 合理的使用Optional

在Java中,为了避免空指针异常,应合理使用Optional类来处理可能为null的对象。

关键观点4: 返回Stream还是返回List

在设计接口时,根据具体场景选择返回Stream或List。Stream提供了不可变性和流处理的优势,而List则更方便直接操作。

关键观点5: 少用或不用并行流

并行流在并发编程中可能存在线程安全问题,并且默认使用的ForkJoinPool可能不够用,因此应谨慎使用。

关键观点6: 代码的可读性和可维护性

写代码不仅是实现功能,更要注重代码的可读性和可维护性。良好的代码风格、清晰的逻辑、注释和文档都是提高代码质量的重要因素。


正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号、CRM 等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本

来源:小姐姐味道


Java8的stream流,加上lambda表达式,可以让代码变短变美,已经得到了广泛的应用。我们在写一些复杂代码的时候,也有了更多的选择。

代码首先是给人看的,其次才是给机器执行的。代码写的是否简洁明了,是否写的漂亮,对后续的bug修复和功能扩展,意义重大。很多时候,是否能写出优秀的代码,是和工具没有关系的。代码是工程师能力和修养的体现,有的人,即使用了stream,用了lambda,代码也依然写的像屎一样。

不信,我们来参观一下一段 美妙 的代码。好家伙,filter里面竟然带着潇洒的逻辑。

public List getFeeds(Query query,Page page){
    List orgiList = new ArrayList<>();
    
    List collect = page.getRecords().stream()
    .filter(this::addDetail)
    .map(FeedItemVo::convertVo)
    .filter(vo -> this.addOrgNames(query.getIsSlow(),orgiList,vo))
    .collect(Collectors.toList());
    //...其他逻辑
    return collect;
}

private boolean addDetail(FeedItem feed){
    vo.setItemCardConf(service.getById(feed.getId()));
    returntrue;
}

private boolean addOrgNames(boolean isSlow,List orgiList,FeedItemVo vo){
    if(isShow && vo.getOrgIds() != null){
        orgiList.add(vo.getOrgiName());
    }
    returntrue;
}

如果觉得不过瘾的话,我们再贴上一小段。

if (!CollectionUtils.isEmpty(roleNameStrList) && roleNameStrList.contains(REGULATORY_ROLE)) {
    vos = vos.stream().filter(
           vo -> !CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList())
                    && vo.getTaskName() != null)
           .collect(Collectors.toList());
else {
    vos = vos.stream().filter(vo -> vo.getIsSelect()
           && vo.getTaskName() != null)
           .collect(Collectors.toList());
    vos = vos.stream().filter(
            vo -> !CollectionUtils.isEmpty(vo.getSpecialTaskItemVoList())
                    && vo.getTaskName() != null)
           .collect(Collectors.toList());
}
result.addAll(vos.stream().collect(Collectors.toList()));

代码能跑,但多画蛇添足。该缩进的不缩进,该换行的不换行,说什么也算不上好代码。

如何改善?除了技术问题,还是一个意识问题。时刻记得,优秀的代码,首先是可读的,然后才是功能完善。

1. 合理的换行

在Java中,同样的功能,代码行数写的少了,并不见得你的代码就好。由于Java使用 ; 作为代码行的分割,如果你喜欢的话,甚至可以将整个Java文件搞成一行,就像是混淆后的JavaScript一样。

当然,我们知道这么做是不对的。在lambda的书写上,有一些套路可以让代码更加规整。

Stream.of("i""am""xjjdog").map(toUpperCase()).map(toBase64()).collect(joining(" "));

上面这种代码的写法,就非常的不推荐。除了在阅读上容易造成障碍,在代码发生问题的时候,比如抛出异常,在异常堆栈中找问题也会变的困难。所以,我们要将它优雅的换行。

Stream.of("i""am""xjjdog")
    .map(toUpperCase())
    .map(toBase64())
    .collect(joining(" "));

不要认为这种改造很没有意义,或者认为这样的换行是理所当然的。在我平常的代码review中,这种糅杂在一块的代码,真的是数不胜数,你完全搞不懂写代码的人的意图。

合理的换行是代码青春永驻的配方。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

2. 舍得拆分函数

为什么函数能够越写越长?是因为技术水平高,能够驾驭这种变化么?答案是因为懒!由于开发工期或者意识的问题,遇到有新的需求,直接往老的代码上添加ifelse,即使遇到相似的功能,也直接选择将原来的代码拷贝过去。久而久之,码将不码。

首先聊一点性能方面的。在JVM中,JIT编译器会对调用量大,逻辑简单的代码进行方法内联,以减少栈帧的开销,并能进行更多的优化。所以,短小精悍的函数,其实是对JVM友好的。

在可读性方面,将一大坨代码,拆分成有意义的函数,是非常有必要的,也是重构的精髓所在。在lambda表达式中,这种拆分更是有必要。

我将拿一个经常在代码中出现的实体转换示例来说明一下。下面的转换,创建了一个匿名的函数 order->{} ,它在语义表达上,是非常弱的。

public Stream getOrderByUser(String userId){
    return orderRepo.findOrderByUser().stream()
        .map(order-> {
            OrderDto dto = new OrderDto();
            dto.setOrderId(order.getOrderId());
            dto.setTitle(order.getTitle().split("#")[0]);
            dto.setCreateDate(order.getCreateDate().getTime());
            return dto;
    });
}

在实际的业务代码中,这样的赋值拷贝还有转换逻辑通常非常的长,我们可以尝试把dto的创建过程给独立开来。因为转换动作不是主要的业务逻辑,我们通常不会关心其中到底发生了啥。

public Stream getOrderByUser(String userId){
    return orderRepo.findOrderByUser().stream()
        .map(this::toOrderDto);
}
public OrderDto toOrderDto(Order order){
    OrderDto dto = new OrderDto();
            dto.setOrderId(order.getOrderId());
            dto.setTitle(order.getTitle().split("#"






请到「今天看啥」查看全文