public List<FeedItemVo> getFeeds(Query query,Page page){ List<String> orgiList = new ArrayList<>(); List<FeedItemVo> 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())); return true; } private boolean addOrgNames(boolean isSlow,List<String> orgiList,FeedItemVo vo){ if(isShow && vo.getOrgIds() != null){ orgiList.add(vo.getOrgiName()); } return true; }如果觉得不过瘾的话,我们再贴上一小段。
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()));代码能跑,但多画蛇添足。该缩进的不缩进,该换行的不换行,说什么也算不上好代码。如何改善?除了技术问题,还是一个意识问题。时刻记得,优秀的代码,首先是可读的,然后才是功能完善。
Stream.of("i", "am", "xjjdog").map(toUpperCase()).map(toBase64()).collect(joining(" "));上面这种代码的写法,就非常的不推荐。除了在阅读上容易造成障碍,在代码发生问题的时候,比如抛出异常,在异常堆栈中找问题也会变的困难。所以,我们要将它优雅的换行。
Stream.of("i", "am", "xjjdog") .map(toUpperCase()) .map(toBase64()) .collect(joining(" "));不要认为这种改造很没有意义,或者认为这样的换行是理所当然的。在我平常的代码review中,这种糅杂在一块的代码,真的是数不胜数,你完全搞不懂写代码的人的意图。合理的换行是代码青春永驻的配方。
为什么函数能够越写越长?是因为技术水平高,能够驾驭这种变化么?答案是因为懒!由于开发工期或者意识的问题,遇到有新的需求,直接往老的代码上添加ifelse,即使遇到相似的功能,也直接选择将原来的代码拷贝过去。久而久之,码将不码。首先聊一点性能方面的。在JVM中,JIT编译器会对调用量大,逻辑简单的代码进行方法内联,以减少栈帧的开销,并能进行更多的优化。所以,短小精悍的函数,其实是对JVM友好的。
public Stream<OrderDto> 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<OrderDto> 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("#")[0]); dto.setCreateDate(order.getCreateDate().getTime()); return dto; }这样的转换代码还是有点丑。但如果OrderDto的构造函数,参数就是Order的话public OrderDto(Order order),那我们就可以把真个转换逻辑从主逻辑中移除出去,整个代码就可以非常的清爽。
public Stream<OrderDto> getOrderByUser(String userId){ return orderRepo.findOrderByUser().stream() .map(OrderDto::new); }除了map和flatMap的函数可以做语义化,更多的filter可以使用Predicate去代替。比如:
Predicate<Registar> registarIsCorrect = reg -> reg.getRegulationId() != null && reg.getRegulationId() != 0 && reg.getType() == 0;registarIsCorrect,就可以当作filter的参数。
if(null == obj) if(null == user.getName() || "".equals(user.getName())) if (order != null) { Logistics logistics = order.getLogistics(); if(logistics != null){ Address address = logistics.getAddress(); if (address != null) { Country country = address.getCountry(); if (country != null) { Isocode isocode = country.getIsocode(); if (isocode != null) { return isocode.getNumber(); } } } } }Java8引入了Optional类,用于解决臭名昭著的空指针问题。实际上,它是一个包裹类,提供了几个方法可以去判断自身的空值问题。
String result = 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的判断。我们要把null消灭在萌芽中。
public Optional<String> getUserName() { return Optional.ofNullable(userName); }另外,我们要尽量的少使用Optional的get方法,它同样会让代码变丑。比如:
Optional<String> userName = "xjjdog"; String defaultEmail = userName.get() == null ? "":userName.get() + "@xjjdog.cn";而应该修改成这样的方式:
Optional<String> userName = "xjjdog"; String defaultEmail = userName .map(e -> e + "@xjjdog.cn") .orElse("");那为什么我们的代码中,依然充满了各式各样的空值判断?即使在非常专业和流行的代码中?一个非常重要的原因,就是Optional的使用需要保持一致。当其中的一环出现了断层,大多数编码者都会以模仿的方式去写一些代码,以便保持与原代码风格的一致。
public Stream<User> getAuthUsers(){ ... return Stream.of(users); }不可变集合是一个强需求,它能防止外部的函数对这些集合进行不可预料的修改。在guava中,就有大量的Immutable类支持这种包裹。再举一个例子,Java的枚举,它的values()方法,为了防止外面的api对枚举进行修改,就只能拷贝一份数据。但是,如果你的api,面向的是最终的用户,不需要再做修改,那么直接返回List就是比较好的,比如函数在Controller中。
List transform(List source){ List dst = new ArrayList<>(); if(CollectionUtils.isEmpty()){ return dst; } source.stream. .parallel() .map(..) .filter(..) .foreach(dst::add); return dst; }
你可能会说,我把foreach改成collect就行了。但是注意,很多开发人员是没有这样的意识的。既然api提供了这样的函数,它在逻辑上又讲得通,那你是阻挡不住别人这么用的。并行流还有一个滥用问题,就是在迭代中执行了耗时非常长的IO任务。在用并行流之前,你有没有一个疑问?既然是并行,那它的线程池是怎么配置的?
Java8加入的Stream功能非常棒,我们不需要再羡慕其他语言,写起代码来也更加行云流水。虽然看着很厉害的样子,但它也只不过是一个语法糖而已,不要寄希望于用了它就获得了超能力。随着Stream的流行,我们的代码里这样的代码也越来越多。但现在很多代码,使用了Stream和Lambda以后,代码反而越写越糟,又臭又长以至于不能阅读。没其他原因,滥用了!
<dependency> <groupId>io.vavr</groupId> <artifactId>vavr</artifactId> <version>0.10.3</version> </dependency>
但无论提供了如何强大的api和编程方式,都扛不住小伙伴的滥用。这些代码,在逻辑上完全是说的通的,但就是看起来别扭,维护起来费劲。写一堆垃圾lambda代码,是虐待同事最好的方式,也是埋坑的不二选择。写代码嘛,就如同说话、聊天一样。大家干着同样的工作,有的人说话好听颜值又高,大家都喜欢和他聊天;有的人不好好说话,哪里痛戳哪里,虽然他存在着但大家都讨厌。