本文转载自微信公众号“小姐姐的味道”,作者小姐姐养的狗。转载本文请联系味觉小姐公众号。Java8的stream,配合lambda表达式,可以让代码更短更漂亮,得到了广泛的应用。当我们编写一些复杂的代码时,我们也有更多的选择。代码首先供人阅读,其次供机器执行。代码是否写得简洁优美,对于后续的bug修复和功能扩展具有重要意义。很多时候,能不能写出优秀的代码,跟工具是没有关系的。代码是工程师能力和修养的体现。有的人,就算用了stream或者lambda,代码写的还是一坨屎。不信,我们来看一段精彩的代码。好家伙,滤镜里有别致的逻辑。publicListgetFeeds(Queryquery,Pagepage){ListorgiList=newArrayList<>();Listcollect=page.getRecords().stream().filter(this::addDetail).map(FeedItemVo::convertVo).filter(vo->this.addOrgNames(query.getIsSlow(),orgiList,vo)).collect(Collectors.toList());//...其他逻辑returncollect;}privatebooleanaddDetail(FeedItemfeed){vo.setItemCardConf(service.getById(feed.getId()));returntrue;}privatebooleanaddOrgNames(booleanisSlow,ListorgiList,FeedItemVovo){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.getSpecialTask??ItemVoList())&&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.getSpecialTask??ItemVoList())&&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(""));不要认为这种转换没有意义,或者认为这样的换行是理所当然的。在我平时的codereview中,真的是无数的代码混在一起,你根本无法理解写代码的人的意图。合理的换行是代码青春永驻的秘诀。2.我愿意拆分功能。为什么函数可以写的越来越长?是不是因为技术水平高,你能控制这种变化?答案是因为懒惰!由于开发周期或意识问题,如果有新的需求,直接在旧的代码中添加ifelse,即使遇到类似的功能,也可以直接选择复制原代码。久而久之,代码就码不出来了。先说性能吧。在JVM中,JIT编译器会内联大量调用和简单逻辑的代码,以减少栈帧的开销,进行更多的优化。所以短小精悍的函数其实对JVM是友好的。在可读性方面,将大量的代码拆分成有意义的功能是非常有必要的,这也是重构的本质所在。在lambda表达式中,这种拆分就更加必要了。我将用代码中经常出现的实体转换示例进行说明。下面的转换创建了一个匿名函数order->{},在语义表达上非常弱。publicStreamgetOrderByUser(StringuserId){returnorderRepo.findOrderByUser().stream().map(order->{OrderDtodto=newOrderDto();dto.setOrderId(order.getOrderId());dto.setTitle(order.getTitle().split("#")[0]);dto.setCreateDate(order.getCreateDate().getTime());returndto;});}在实际业务代码中,这样的赋值拷贝和转换逻辑通常是很long,我们可以尝试把dto的创建过程独立出来。因为转换动作不是主要的业务逻辑,所以我们通常不关心里面发生了什么。publicStreamgetOrderByUser(StringuserId){returnorderRepo.findOrderByUser().stream().map(this::toOrderDto);}publicOrderDtotoOrderDto(Orderorder){OrderDtodto=newOrderDto();dto.setOrderId(order.getOrderId());转换代码如dto.setTitle(order.getTitle().split("#")[0]);dto.setCreateDate(order.getCreateDate().getTime());returndto;}还是有点难看。但是如果OrderDto的构造函数的参数是Order,publicOrderDto(Orderorder),那我们就可以把真正的转换逻辑从主逻辑中去掉,整个代码就可以很干净了。publicStreamgetOrderByUser(StringuserId){returnorderRepo.findOrderByUser().stream().map(OrderDto::new);}除了map和flatMap的功能可以语义化外,更多的过滤器可以用Predicate代替。例如:PredicateregistarIsCorrect=reg->reg.getRegulationId()!=null&®.getRegulationId()!=0&®.getType()==0;registarIsCorrect可以用作过滤器参数。3.合理使用Optional在Java代码中,由于NullPointerException不是强制捕获的异常,它会隐藏在代码中,造成很多不可预知的bug。因此,当我们拿到一个参数的时候,我们会验证它的有效性,看它是否为null。代码中充满了这样的代码。if(null==obj)if(null==user.getName()||"".equals(user.getName()))if(order!=null){Logisticslogistics=order.getLogistics();if(logistics!=null){Addressaddress=logistics.getAddress();if(address!=null){Countrycountry=address.getCountry();if(country!=null){Isocodeisocode=country.getIsocode();if(isocode!=null){returnisocode.getNumber();}}}}}Java8引入了Optional类来解决臭名昭著的空指针问题。其实就是一个包装类,提供了几种判断自身空值问题的方法。上面更复杂的代码示例可以替换为以下代码。Stringresult=Optional.ofNullable(order).flatMap(order->order.getLogistics()).flatMap(logistics->logistics.getAddress()).flatMap(address->address.getCountry()).map(country->country.getIsocode()).orElse(Isocode.CHINA.getNumber());当你不确定自己提供的是不是空的时候,一个好的习惯是不要返回null,否则调用者的代码会全是null的判断。我们想将无效的结果消灭在萌芽状态。publicOptionalgetUserName(){returnOptional.ofNullable(userName);}另外我们要尽量少用可选的get方法,这样也会让代码变丑。例如:OptionaluserName="xjjdog";StringdefaultEmail=userName.get()==null?"":userName.get()+"@xjjdog.cn";而应该修改成这样:OptionaluserName="xjjdog";StringdefaultEmail=userName.map(e->e+"@xjjdog.cn").orElse("");那为什么我们的代码里还是充斥着各种空值判断呢?即使是非常专业和流行的代码?一个很重要的原因是Optional的使用需要保持一致。当其中一个环节出现空隙时,为了保持与原代码风格的一致性,大多数编码人员都会模仿的方式编写一些代码。想要在项目中普及Optional的使用,脚手架设计者或者review者还需要更加努力。4、返回Stream还是返回List?很多人在设计界面的时候都会陷入进退两难的境地。是直接返回数据到Stream还是List?如果你返回一个List,比如ArrayList,那么如果你修改这个List,会直接影响到里面的值,除非你用不可变的方式包裹起来。同样,数组也有这个问题。但是对于Stream来说,它是不可变的,不会影响原来的集合。对于这种场景,我们建议直接返回一个Stream而不是一个集合。这种做法的另一个好处是,它可以强烈暗示API用户应该使用更多Stream相关的功能来统一代码风格。publicStreamgetAuthUsers(){...returnStream.of(users);}不可变集合是一个强烈的需求,它可以防止外部函数对这些集合进行意外更改。在guava中,有大量的Immutable类支持这种包装。再举个例子,Java的枚举,它的values()方法,为了防止外部API修改枚举,只能复制一份数据。但是,如果你的API是针对终端用户的,不需要修改,那么直接返回List比较好,比如函数在Controller中。5、少用或不用并行流Java的并行流有很多问题,这些问题对于不熟悉并发编程的人来说经常踩坑。并不是并行流不好,但是如果你发现你的团队总是被它绊倒,那么你会毫不犹豫地减少推荐的频率。并行流的一个老问题是线程安全。在迭代过程中,如果使用线程不安全的类,很容易出问题。例如,下面的代码在大多数情况下都是错误的。Listtransform(Listsource){Listdst=newArrayList<>();if(CollectionUtils.isEmpty()){returndst;}source.stream..parallel().map(..).filter(..).foreach(dst::add);returndst;}你可能会说,我只是将foreach更改为收集。但请注意,许多开发人员并没有这样的意识。既然api提供了这样的功能,而且合乎逻辑,你就不能阻止别人这样用。还有一个滥用并行流的问题,就是在迭代中执行很长的IO任务。在使用并行流之前,您有什么疑问吗?既然是并行,那么它的线程池是怎么配置的呢?不幸的是,所有并行流共享一个ForkJoinPool。它的大小,默认是CPU个数-1,大多数情况下是不够用的。如果有人在并行流上运行耗时的IO业务,那么即使你进行一个简单的数学运算,你也需要排队。关键是,你无法阻止项目中的其他同学使用并行流,你也无法知道他做了什么。所以我该怎么做?我的做法是一刀切,直接禁止。虽然残酷,但它避免了问题。综上所述,Java8新增的Stream功能非常好。我们不用再羡慕其他语言了,写代码也更流畅了。虽然它看起来很强大,但它只是一个语法糖。不要希望你会通过使用它来获得超能力。随着Stream的普及,我们的代码中这样的代码越来越多。但是现在很多代码,用了Stream和Lambda之后,代码越来越差,又臭又长,看不懂。没有别的原因,就是被虐了!一般来说,在使用Stream和Lambda的时候,一定要保证主流程简单明了,风格要统一,合理换行,愿意增加功能,正确使用Optional特性,不要使用filter等功能里加代码逻辑。在编写代码时,请自觉遵循这些小提示。简洁和优雅是生产力。如果你觉得Java提供的功能还不够,那么我们还有一个开源类库vavr,它提供了更多的可能性,可以和Stream、Lambda结合,提升函数式编程的体验。io.vavrvavr0.10.3但是无论提供多么强大的api和编程方法,都无法承载停止虐待你的朋友。这些代码在逻辑上是合理的,但就是看起来笨拙,维护起来也很费力。写一堆垃圾lambda代码是辱骂同事最好的方式,也是埋坑的唯一选择。写代码就像说话聊天。每个人都在做同样的工作。有些人说话好听,长得好看。每个人都喜欢和他聊天;代码,除了工作的意义,只是我们在世界上表达想法的另一种方式。如何写出好的代码不仅是一个技术问题,更是一个意识问题。作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。