被人說(shuō) Lambda 代碼像...,那是沒(méi)用下面這三個(gè)方法
說(shuō) Lambda 寫(xiě)的代碼像屎山,其實(shí)就是代碼不夠干凈嘛。說(shuō)到底并不是不會(huì)用 Lambda 本身的 API,而是用的方式不對(duì)。
Java Lambda 本身提供了非常豐富的方法庫(kù),大多數(shù)時(shí)候我們常用的方法也就為數(shù)不多的那幾個(gè)。Lambda 的使用方法之前專(zhuān)門(mén)寫(xiě)過(guò)文章,8000字,讓你徹底了解 Java 8 的 Lambda、函數(shù)式接口、Stream 用法和原理。在掘金社區(qū)已經(jīng)獲得了將近600個(gè)贊,1200多個(gè)收藏。
之所以被diss,也大概并不是你用了其中某個(gè)不常用的方法(那樣別人可能覺(jué)得你見(jiàn)多識(shí)廣)。更多的時(shí)候可能是因?yàn)檫@幾個(gè)原因:
- 代碼寫(xiě)太亂了。一個(gè)人說(shuō)亂那可能是那個(gè)人的問(wèn)題,如果大家都說(shuō)亂,不好意思,那基本上就是你的問(wèn)題了。
- Lambda 函數(shù)式本身的寫(xiě)法,尤其是對(duì)于從未接觸過(guò)函數(shù)式編程的開(kāi)發(fā)來(lái)說(shuō),這種寫(xiě)法本身就不太習(xí)慣,甚至不喜歡。負(fù)面情緒先入為主了,自然就覺(jué)得亂了。
- 還有就是一直被詬病調(diào)試問(wèn)題,Lambda 公認(rèn)的不便于調(diào)試。
先來(lái)看一段代碼,也就是經(jīng)常被人(除自己外的所有人)說(shuō)的屎山代碼。
private static List<SimpleUser> dirtyLambda(){
List<User> userList = User.buildUserList(30);
List<SimpleUser> simpleUserList = userList.stream()
.filter(user -> {
return user.getGender().equals(1)
&& user.getAge() >= 18 && user.getAge() <= 45;
})
.map(user -> {
SimpleUser su = new SimpleUser();
su.setName(user.getName());
su.setAge(user.getAge());
Optional<Address> addressOptional = user.getAddressList().stream()
.findFirst();
if (addressOptional.isPresent()) {
su.setProvince(addressOptional.get().getProvince());
}
return su;
})
.sorted(Comparator.comparingInt(SimpleUser::getAge))
.collect(Collectors.toList());
return simpleUserList;
}
如果不做解釋?zhuān)遣皇桥K話?cǎi)R上就要出來(lái)了。這其實(shí)在屎山代碼中也最多拍個(gè)中等,最起碼該換行的換行了,比如那個(gè)filter 中的三個(gè)并列條件,恐怕你是沒(méi)見(jiàn)過(guò)與或非排列組合的寫(xiě)法,加上不怎么換行,那是真的讓人抓狂。
如果你覺(jué)得這代碼還可以,那有可能你也這么寫(xiě)過(guò)。
不瞞各位,這樣的代碼我曾經(jīng)寫(xiě)過(guò),而且一天之內(nèi)不知道寫(xiě)了多少行。曾經(jīng)有一個(gè)需求,一個(gè)很復(fù)雜的報(bào)表,100多個(gè)變量+圖表+表格,什么最大值、最小值、環(huán)比、同比、正序、倒序、top3、top5、top10等等,就是各種能想到的維度統(tǒng)統(tǒng)算一遍。有經(jīng)驗(yàn)的同學(xué)一看就知道,這妥妥的體力活兒啊,但是時(shí)間只有一天,沒(méi)辦法,越寫(xiě)越煩躁,直接躺平了,比上面這種更屎的代碼一段接一段的寫(xiě)啊。寫(xiě)完別說(shuō)改了,看都不敢看啊。
說(shuō)回正題,上面那個(gè)代碼的邏輯是這樣的:
- 在一個(gè) User列表中篩選男性,且年齡為18到45歲之間的;
- 然后將 User轉(zhuǎn)換為 SimpleUser類(lèi)型,獲取姓名、年齡,以及地址列表(假設(shè)一個(gè)人有多個(gè)地址)中第一個(gè)的省份字段;
- 然后排序,按照年齡正序排序;
- 最后返回一個(gè) SimpleUser 列表;
那怎么做才能讓代碼變得清晰易懂,告別屎山 Lambda 呢?
不管你用什么辦法,只要做到下面這3點(diǎn),Lambda 代碼塊立馬變清晰,最后一點(diǎn)可以適度放寬。
不要超過(guò) 5 行
這其實(shí)沒(méi)什么好說(shuō)的,本身代碼規(guī)約中就要求最好不寫(xiě)超大方法,也就是行數(shù)過(guò)多的方法,更何況是在 Lambda 中。在函數(shù)式編程中,你寫(xiě)的代碼其實(shí)是在小括號(hào)中,作為參數(shù)的形式出現(xiàn)的,一個(gè)多行的參數(shù),不敢想啊。
不超過(guò)5行可不是說(shuō)把換行符去掉,把之前的100行直接邊 1 行啊。而是下面這樣子,stream()就算一行了, 之后每個(gè).function()都算一行,加起來(lái)不超過(guò)5行。
userList.stream()
.filter()
.filter()
.map()
.collect(Collectors.toList());
不要一個(gè)stream() 后面跟3個(gè)filter,4個(gè)map,再來(lái)個(gè)排序,再整個(gè)分組,有那么復(fù)雜的業(yè)務(wù)嗎,如果有,想想可能在上層設(shè)計(jì)的時(shí)候就出現(xiàn)問(wèn)題了。
不要出現(xiàn)花括號(hào)
不要出現(xiàn)花括號(hào),這其實(shí)就是縮短代碼行數(shù)的一個(gè)根本方法。用這個(gè)方法,強(qiáng)制你將邏輯抽離出來(lái),這樣,你的代碼邏輯就會(huì)馬上變清晰,立竿見(jiàn)影。
拿前面的那端代碼舉個(gè)例子,其中map方法將 User轉(zhuǎn)換為SimpleUser,里面有賦值操作,還有一些判斷邏輯。
.map(user -> {
SimpleUser su = new SimpleUser();
su.setName(user.getName());
su.setAge(user.getAge());
Optional<Address> addressOptional = user.getAddressList().stream()
.findFirst();
if (addressOptional.isPresent()) {
su.setProvince(addressOptional.get().getProvince());
}
return su;
})
直接將一段抽取成方法,在 IDEA 中操作也非常方便。選中花括號(hào)中的代碼,然后右鍵->Refactor->Extract Method,直接抽取出方法,連名字都幫忙取號(hào)了。
圖片
同樣的,filter()中的三個(gè)條件判斷也抽離出來(lái)。然后效果就是下面這樣,每一行的意圖都很清晰,誰(shuí)還會(huì)說(shuō)不能理解。
private static List<SimpleUser> dirtyLambda(){
List<User> userList = User.buildUserList(30);
List<SimpleUser> simpleUserList = userList.stream()
.filter(user -> filterUser(user))
.map(user -> getSimpleUser(user))
.sorted(Comparator.comparingInt(SimpleUser::getAge))
.collect(Collectors.toList());
return simpleUserList;
}
private static boolean filterUser(User user) {
return user.getGender().equals(1)
&& user.getAge() >= 18 && user.getAge() <= 45;
}
private static SimpleUser getSimpleUser(User user) {
SimpleUser su = new SimpleUser();
su.setName(user.getName());
su.setAge(user.getAge());
Optional<Address> addressOptional = user.getAddressList().stream()
.findFirst();
if (addressOptional.isPresent()) {
su.setProvince(addressOptional.get().getProvince());
}
return su;
}
最好連 -> 都不要出現(xiàn)
再進(jìn)一步,就是將 ->也干掉,雖然 ->后面沒(méi)有花括號(hào)已經(jīng)很簡(jiǎn)潔了,但是去掉->就不只是簡(jiǎn)潔了,而是優(yōu)雅了。
不用->,取而代之的是 ::,最終,去掉->后的代碼是下面這樣子。
private static List<SimpleUser> dirtyLambda(){
List<User> userList = User.buildUserList(30);
return userList.stream()
.filter(CleanLambda::filterUser)
.map(CleanLambda::getSimpleUser)
.sorted(Comparator.comparingInt(SimpleUser::getAge))
.collect(Collectors.toList());
}
private static boolean filterUser(User user) {
return user.getGender().equals(1)
&& user.getAge() >= 18 && user.getAge() <= 45;
}
private static SimpleUser getSimpleUser(User user) {
SimpleUser su = new SimpleUser();
su.setName(user.getName());
su.setAge(user.getAge());
Optional<Address> addressOptional = user.getAddressList().stream()
.findFirst();
addressOptional.ifPresent(address -> su.setProvince(address.getProvince()));
return su;
}
最后
本文只是拋磚引玉,并沒(méi)有介紹太細(xì)節(jié)的 Lambda 用法。授人以魚(yú)不如授人以漁,聰明人早就這樣寫(xiě)了,更聰明的人已經(jīng)去改代碼了。