滿滿干貨?。?!lambda表達(dá)式使用總結(jié)

前言
最近一段時(shí)間一直在趕項(xiàng)目,所以也比較忙,今天我抽一些時(shí)間,把整個(gè)項(xiàng)目參與的收獲和各位小伙伴分享下,也算是一個(gè)小階段的總結(jié),不過(guò)今天的主題是lambda,所以只能算是局部知識(shí)點(diǎn)的總結(jié)。
由于整個(gè)項(xiàng)目都采用redis存儲(chǔ)數(shù)據(jù)(你沒(méi)聽(tīng)錯(cuò),就是拿redis當(dāng)數(shù)據(jù)庫(kù)),所以在數(shù)據(jù)讀寫(xiě)方面,特別是查詢的時(shí)候,很多場(chǎng)景下都是拿到數(shù)據(jù)后,還需要手動(dòng)處理下,所以本次對(duì)lambda使用的頻次特別高,今天我們就先抽點(diǎn)時(shí)間做一個(gè)簡(jiǎn)單的梳理和總結(jié)。
拉姆達(dá)表達(dá)式
提到lambda表達(dá)式,想必各位小伙伴一定不會(huì)感到陌生,lambda是JDK1.8引入的新特性,而且經(jīng)常在面試的時(shí)候被問(wèn)道,更重要的是使用lambda確實(shí)也可以讓我們的代碼更簡(jiǎn)潔,很多場(chǎng)景也更容易實(shí)現(xiàn)。前面我們也分享過(guò)lambda的相關(guān)知識(shí)點(diǎn),感興趣的小伙伴可以去看下:



今天我們主要lambda中的以下幾塊內(nèi)容,這幾個(gè)也是本次用的頻次比較高的:
mapfilterjoiningmax排序 flatMapcollect
map
map其實(shí)就類(lèi)似于一個(gè)處理器,我們可以通過(guò)map方法將現(xiàn)有對(duì)象的集合轉(zhuǎn)換成我們需要的數(shù)據(jù)類(lèi)型。
過(guò)濾屬性
其中最常見(jiàn)的用法就是從一個(gè)對(duì)象實(shí)例的list中,過(guò)濾出對(duì)象實(shí)例的某個(gè)屬性,比如User的name`屬性:
User?user1?=?new?User(1L,?"syske");
User?user2?=?new?User(2L,?"yun?zhong?zhi");
List?userList?=?Lists.newArrayList(user1,?user2);
//?用戶
System.out.printf("userList?=?%s\n",userList);
//?用戶名list
List?userNameList?=?userList.stream().map(User::getUsername).collect(Collectors.toList());
System.out.printf("userNameList?=?%s\n",userNameList);
運(yùn)行結(jié)果如下:

user源碼:
public?class?User?{
????/**
?????*?用戶?id
?????*/
????private?Long?id;
????/**
?????*?用戶名
?????*/
????private?String?username;
????/*
????省略getter/setter方法
????*/
}
這里我們其實(shí)也演示了collect方法的簡(jiǎn)單用法,collect方法的作用就是將我們map處理之后的數(shù)據(jù)流收集起來(lái),后面我們還會(huì)詳細(xì)演示它的用法。
轉(zhuǎn)換類(lèi)型
這里說(shuō)的類(lèi)型轉(zhuǎn)換主要也是將List中的數(shù)據(jù)轉(zhuǎn)換成我們需要的,比如將user轉(zhuǎn)換為String:
List?userStrList?=?userList.stream().map(String::valueOf).collect(Collectors.toList());
或者轉(zhuǎn)換為json:
List?collect?=?userList.stream().map(JSON::toJSONString).collect(Collectors.toList());
或者將String轉(zhuǎn)成Integer:
ArrayList?strings?=?Lists.newArrayList("0912",?"1930",?"1977",?"1912");
List?integers?=?strings.stream().map(Integer::parseInt).collect(Collectors.toList());
當(dāng)然,map的用法會(huì)有很多,感興趣的小伙伴自行探索下吧,下面我們看下其他lambda表達(dá)式的用法。
filter
filter也是日常開(kāi)發(fā)中經(jīng)常用到的一個(gè)表達(dá)式,而且非常好用,比如數(shù)據(jù)檢索、數(shù)據(jù)過(guò)濾等:
ArrayList?countList?=?Lists.newArrayList(89,?97,?99,?12,?15,?45,?55,?35,?25,?18);
//?過(guò)濾大于?40的數(shù)據(jù)
List?collect1?=?countList.stream().filter(a?->?a?>?40).collect(Collectors.toList());
//?過(guò)濾結(jié)果:[89, 97, 99, 45, 55]
過(guò)濾用戶名中包含s的用戶:
User?user1?=?new?User(1L,?"syske");
User?user2?=?new?User(2L,?"yun?zhong?zhi");
List?userList?=?Lists.newArrayList(user1,?user2);
//?過(guò)濾username包含s的用戶
List?users?=?userList.stream().filter(user?->?user.getUsername().contains("s")).collect(Collectors.toList());
joining
joining主要是針對(duì)List聚合成string的場(chǎng)景,它主要用于需要將集合中的元素通過(guò)特定的符號(hào)拼接,比如,分割:
ArrayList?ages?=?Lists.newArrayList(89,?97,?99,?12,?15,?45,?55,?35,?25,?18);
String?collect2?=?ages.stream().map(String::valueOf).collect(Collectors.joining(","));
//?運(yùn)行結(jié)果:89,97,99,12,15,45,55,35,25,18
因?yàn)樽罱K的結(jié)果是String,所以在joining之前先要通過(guò)map處理下數(shù)據(jù),如果數(shù)據(jù)是String類(lèi)型,則可以直接操作:
ArrayList?strings?=?Lists.newArrayList("0912",?"1930",?"1977",?"1912");
String?collect3?=?strings.stream().collect(Collectors.joining());
嚴(yán)格來(lái)說(shuō),joining操作屬于collect方法的范疇,和Collectors.toList屬于同一類(lèi)操作。
max
max就很簡(jiǎn)單了,就是獲取集合中的最大值,和min相對(duì)。支持對(duì)數(shù)字、字符串等數(shù)據(jù)進(jìn)行操作:
Optional?max?=?ages.stream().max(Comparator.naturalOrder());
System.out.println(max.get());
這里的Comparator.naturalOrder就是安裝自然順序排序,也就是9-1,或者z-a,我測(cè)試的時(shí)候發(fā)現(xiàn),如果存在相同字符(但是大小寫(xiě)不同,針對(duì)字符串),排序的時(shí)候是按照小寫(xiě)大于大寫(xiě)的規(guī)則進(jìn)行排序的,具體可以看下面的截圖:

如果數(shù)據(jù)是數(shù)字:
ArrayList?ages?=?Lists.newArrayList(89,?97,?99,?12,?15,?45,?55,?35,?25,?18);
Optional?max?=?ages.stream().max(Comparator.naturalOrder());
//?運(yùn)行結(jié)果:99
這里有個(gè)騷操作,如果把Comparator.naturalOrder()改成Comparator.reverseOrder()獲取到的就是最小值,我在想如果你就是想寫(xiě)這樣一個(gè)讓別人想不到的bug,你可以試試這樣操作,看會(huì)不會(huì)被打死。
排序
排序其實(shí)在上面max以及有提及了,這里我們?cè)僭敿?xì)看下。一般我們并不會(huì)單通過(guò)stream進(jìn)行排序,因?yàn)槌S玫募匣旧隙继峁┝伺判蚍椒ǎ热?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">List:
ArrayList?strings2?=?Lists.newArrayList("ab",?"ba",?"ca",?"dd",?"Zf",?"ZF",?"zF",?"zf",?"cl");
strings2.sort(Comparator.naturalOrder());
只有在stream處理之后,數(shù)據(jù)還要求有順序要求的時(shí)候,我們才會(huì)通過(guò)stream進(jìn)行排序:
ArrayList?ages?=?Lists.newArrayList(89,?97,?99,?12,?15,?45,?55,?35,?25,?18);
List?collect1?=?ages.stream().filter(a?->?a?>?40).sorted(Comparator.naturalOrder()).collect(Collectors.toList());
這樣處理完成之后的數(shù)據(jù)就是有序集合了,這樣的性能會(huì)比拿到集合之后再排序要好。
flatMap
flatMap和map有點(diǎn)像,當(dāng)然區(qū)別也挺大,map其實(shí)就類(lèi)似水管中單進(jìn)單出的處理器,進(jìn)去多少個(gè),出來(lái)多少個(gè),而flatMap是單入多出的處理器,進(jìn)去一個(gè),出來(lái)可能是多個(gè)。下面我們看下他們的對(duì)比:
//?原始數(shù)據(jù)
ArrayList?strings3?=?Lists.newArrayList("09,12",?"19,30",?"19,77",?"19,12");
//?map處理
List?collect4?=?strings3.stream().map(string?->?string.split(",")).collect(Collectors.toList());
//?flatMap
List?collect5?=?strings3.stream().flatMap(string?->?Arrays.stream(string.split(","))).collect(Collectors.toList());
可以看到map只能把數(shù)據(jù)最終分割成與原有元素?cái)?shù)量相等的數(shù)據(jù)數(shù)組,而flatMap這里可以進(jìn)一步將數(shù)據(jù)進(jìn)行分割,最終直接返回我們的目標(biāo)數(shù)據(jù),進(jìn)一步說(shuō)就是flatMap可以改造流,而map只能在流的基礎(chǔ)上處理。
另外,需要注意的是,流和水管中的水一樣,一旦被處理之后(流過(guò))就不存在了,所以是沒(méi)有辦法作為參數(shù)被頻繁使用。
collect
toMap
collect我們前面的示例一直都在使用,和其他方法相比,collect就是流的終點(diǎn),也就是最終的收集器,collect除了可以把數(shù)據(jù)收集到List、Set中,還可以把數(shù)據(jù)處理成map
User?user1?=?new?User(1L,?"syske");
????????User?user2?=?new?User(2L,?"yun?zhong?zhi");
????????List?userList?=?Lists.newArrayList(user1,?user2);
//?構(gòu)建userId,userName?集合
Map?collect6?=?userList.stream().collect(Collectors.toMap(User::getId,?User::getUsername));
//?構(gòu)建userId,用戶集合
Map?collect7?=?userList.stream().collect(Collectors.toMap(User::getId,?Function.identity()));
需要注意的是,toMap需要避免key沖突,通常情況下我們只需要多加一個(gè)參數(shù)即可解決問(wèn)題:
Map?collect6?=?userList.stream().collect(Collectors.toMap(User::getId,?User::getUsername,?(a,?b)?->?a));
這里的(a, b) -> a意思就是如果key已經(jīng)存在,則保留舊的值,這一點(diǎn)可以從源碼中看出來(lái):

如果舊的值為null,則新值直接覆蓋,否則根據(jù)我們的策略取值,即用舊值。
groupBy
除了上面的toMap,下面這個(gè)就更方便了,可以直接根據(jù)數(shù)據(jù)的某個(gè)屬性分組,最終返回屬性對(duì)應(yīng)的map,比如這里
User?user1?=?new?User(1L,?"syske");
User?user2?=?new?User(2L,?"yun?zhong?zhi");
User?user3?=?new?User(2L,?"yun?zhong?zhi?2");
List?userList?=?Lists.newArrayList(user1,?user2,?user3);
Map>?collect8?=?userList.stream().collect(Collectors.groupingBy(User::getId));
//?運(yùn)行結(jié)果如下:
//?{1=[User{id=1,?username='syske'}],?2=[User{id=2,?username='yun?zhong?zhi'},?User{id=2,?username='yun?zhong?zhi?2'}]}
除了上面這種,還有其他更方便更強(qiáng)的操作,比如我想分組之后統(tǒng)計(jì)數(shù)量:
Map?collect9?=?userList.stream().collect(Collectors.groupingBy(User::getId,?Collectors.counting()));
//?運(yùn)行結(jié)果如下(元數(shù)據(jù)同上):{1=1, 2=2}
需要注意的是,Collectors.counting()返回的是Long。
再比如分組之后我們還需要對(duì)某個(gè)字段求和:
Map?collect10?=?userList.stream().collect(Collectors.groupingBy(User::getId,?Collectors.summingLong(User::getId)));
//?運(yùn)行結(jié)果如下(元數(shù)據(jù)同上):{1=1, 2=4}
再比如分組之后求平均值:
Map?collect11?=?userList.stream().collect(Collectors.groupingBy(User::getId,?Collectors.averagingLong(User::getId)));
//?運(yùn)行結(jié)果如下(元數(shù)據(jù)同上):{1=1.0, 2=2.0}
好了,關(guān)于groupBy我們就說(shuō)這么多,還有其他需求的小伙伴可以自己再研究下。
其他
collect這塊除了我們上面提到的,還有幾個(gè)比較常用的,其實(shí)就是我們?cè)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">groupBy那塊組合用到的,比如求平均值:
Double?collect12?=?userList.stream().collect(Collectors.averagingLong(User::getId));
求和:
Long?collect13?=?userList.stream().collect(Collectors.summingLong(User::getId));
還有我們前面說(shuō)的joining、maxBy、minBy、counting等,就不一一列舉了,因?yàn)榉椒▽?shí)在是太多了。
結(jié)語(yǔ)
應(yīng)該說(shuō)從jdk1.8開(kāi)始,lambda讓java編程更優(yōu)雅也更簡(jiǎn)便,但這并不是推薦你在日常開(kāi)發(fā)中全部使用lambda表達(dá)式,畢竟在某些場(chǎng)景下,lambda性能并不好,關(guān)于這塊我們之前是有測(cè)試結(jié)果的:

當(dāng)然如果用parallelStream會(huì)解決性能問(wèn)題,但是在使用parallelStream的時(shí)候盡可能不要用到外部變量,否則會(huì)導(dǎo)致線程安全問(wèn)題,這個(gè)我踩過(guò)坑??傊褪悄阋龑W(xué)會(huì)把握使用lambda的場(chǎng)景,在一些性能差別不是特別大的場(chǎng)景下,用lamdba會(huì)讓你的代碼更簡(jiǎn)潔,更容易理解,最重要的是可以寫(xiě)更少的代碼,提升開(kāi)發(fā)效率。
今天雖然分享的內(nèi)容有點(diǎn)多,但都是滿滿的干貨,是我最近一段時(shí)間工作中使用lambda的一點(diǎn)點(diǎn)總結(jié),希望可以真正幫到各位小伙伴,好了,晚安吧!
