java中l(wèi)ambda表達(dá)式簡(jiǎn)單用例
我對(duì)java中l(wèi)ambda表達(dá)式的看法是相當(dāng)糾結(jié)的:
一個(gè)我這么想:lambda表達(dá)式降低了java程序的閱讀體驗(yàn)。java程序一直不以表現(xiàn)力出眾,正相反使Java流行的一個(gè)因素正是它的安全和保守——即使是初學(xué)者只要注意些也能寫出健壯且容易維護(hù)的代碼來(lái)。lambda表達(dá)式對(duì)開發(fā)人員的要求相對(duì)來(lái)說(shuō)高了一層,因此也增加了一些維護(hù)難度。
另一個(gè)我這么想:作為一個(gè)碼代碼的,有必要學(xué)習(xí)并接受語(yǔ)言的新特性。如果只是因?yàn)樗拈喿x體驗(yàn)差就放棄它在表現(xiàn)力方面的長(zhǎng)處,那么即使是三目表達(dá)式也有人覺(jué)得理解起來(lái)困難呢。語(yǔ)言也是在發(fā)展的,跟不上的就自愿被丟下好了。
我不愿意被丟下。不過(guò)非讓我做出選擇的話,我的決定還是比較保守的:沒(méi)必要一定在java語(yǔ)言中使用lambda——它讓目前Java圈子中的很多人不習(xí)慣,會(huì)造成人力成本的上升。如果非常喜歡的話,可以考慮使用scala。
不管怎樣,我還是開始試著掌握Lambda了,畢竟工作中維護(hù)的部分代碼使用了Lambda(相信我,我會(huì)逐步把它去掉的)。學(xué)習(xí)的教程是在Oracla – Java官網(wǎng)的相關(guān)教程。
——————————
假設(shè)目前正在創(chuàng)建一個(gè)社交網(wǎng)絡(luò)應(yīng)用。其中的一個(gè)特性是管理員可以對(duì)符合指定條件的會(huì)員執(zhí)行某些操作,如發(fā)送消息。下面的表格詳細(xì)描述了這個(gè)用例:
Field | 描述 |
名稱 | 要執(zhí)行的操作 |
主要參與者 | 管理員 |
前提條件 | 管理員登錄系統(tǒng) |
后置條件 | 只對(duì)符合指定標(biāo)準(zhǔn)的會(huì)員執(zhí)行操作 |
主成功場(chǎng)景 | 1. 管理員對(duì)要執(zhí)行操作的目標(biāo)會(huì)員設(shè)置過(guò)濾標(biāo)準(zhǔn); 2. 管理員選擇要執(zhí)行的操作; 3. 管理員點(diǎn)擊提交按鈕; 4. 系統(tǒng)找到符合指定標(biāo)準(zhǔn)的會(huì)員; 5. 系統(tǒng)對(duì)符合指定標(biāo)準(zhǔn)的會(huì)員執(zhí)行預(yù)先選擇的操作。 |
擴(kuò)展 | 在選擇執(zhí)行操作前或者點(diǎn)擊提交按鈕前,管理員可以選擇是否預(yù)覽符合過(guò)濾標(biāo)準(zhǔn)的會(huì)員信息。 |
發(fā)生頻率 | 一天中會(huì)發(fā)生許多次。 |
使用下面的Person類來(lái)表示社交網(wǎng)絡(luò)中的會(huì)員信息:
public class Person { public enum Sex { MALE, FEMALE } String name; LocalDate birthday; Sex gender; String emailAddress; public int getAge() { // ... } public void printPerson() { // ... } }
假設(shè)所有的會(huì)員都保存在一個(gè)List<Person>實(shí)例中。
這一節(jié)我們從一個(gè)非常簡(jiǎn)單的方法開始,然后嘗試使用局部類和匿名類進(jìn)行實(shí)現(xiàn),到最后會(huì)逐步深入體驗(yàn)Lambda表達(dá)式的強(qiáng)大和高效??梢栽谶@里找到完整的代碼。
方案一:一個(gè)個(gè)地創(chuàng)建查找符合指定標(biāo)準(zhǔn)的會(huì)員的方法
這是實(shí)現(xiàn)前面提到的案例最簡(jiǎn)單粗糙的方案了:就是創(chuàng)建幾個(gè)方法、每個(gè)方法校驗(yàn)一項(xiàng)標(biāo)準(zhǔn)(比如年齡或是性別)。下面的一段代碼校驗(yàn)了年齡大于一個(gè)指定值的情況:
public static void printPersonsOlderThan(List<person> roster, int age) { for (Person p : roster) { if (p.getAge() >= age) { p.printPerson(); } } }
這是一種很脆弱的方案,極有可能因?yàn)橐稽c(diǎn)更新就導(dǎo)致應(yīng)用無(wú)法運(yùn)行。假如我們?yōu)镻erson類添加了新的成員變量或者更改了標(biāo)準(zhǔn)中衡量年齡的算法,就需要重寫大量的代碼來(lái)適應(yīng)這種變化。再者,這里的限制也太過(guò)僵化了,比方說(shuō)我們要打印年齡小于某個(gè)指定值的成員又該怎么做呢?再添加一個(gè)新方法printPersonsYoungerThan?這顯然是一個(gè)笨方法。
方案二:創(chuàng)建更通用的方法
下面的方法較之printPersonsOlderThan適應(yīng)性更好一些;這個(gè)方法打印了在指定年齡段內(nèi)的會(huì)員信息:
public static void printPersonsWithinAgeRange( List<person> roster, int low, int high) { for (Person p : roster) { if (low <= p.getAge() && p.getAge() < high) { p.printPerson(); } } }
此時(shí)又有新的想法了:如果我們要打印指定性別的會(huì)員信息,或者同時(shí)符合指定性別又在指定年齡段內(nèi)的會(huì)員信息該怎么辦呢?如果我們調(diào)整了Person類,添加了諸如友好度和地理位置這樣的屬性又該怎么辦呢。盡管這樣寫方法要比printPersonsYoungerThan通用性更強(qiáng)一些,但是如果為每一種可能的查詢都寫一個(gè)方法也會(huì)導(dǎo)致代碼的脆弱。倒不如把標(biāo)準(zhǔn)檢查這一塊代碼給獨(dú)立到一個(gè)新的類中。
方案三:在一個(gè)局部類中實(shí)現(xiàn)標(biāo)準(zhǔn)檢查
下面的方法打印了符合檢索標(biāo)準(zhǔn)的會(huì)員信息:
public static void printPersons(List<person> roster, CheckPerson tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }
在程序里使用了一個(gè)CheckPerso對(duì)象tester對(duì)List參數(shù)roster中的每個(gè)實(shí)例進(jìn)行校驗(yàn)。如果tester.test()返回true,就會(huì)執(zhí)行printPerson()方法。為了設(shè)置檢索標(biāo)準(zhǔn),需要實(shí)現(xiàn)CheckPerson接口。
下面的這個(gè)類實(shí)現(xiàn)了CheckPerson并提供了test方法的具體實(shí)現(xiàn)。這個(gè)類中的test方法過(guò)濾了滿足在美國(guó)服兵役條件的會(huì)員信息:即性別為男、且年齡在18~25歲之間。
class CheckPersonEligibleForSelectiveService implements CheckPerson { public boolean test(Person p) { return p.gender == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } }
要使用這個(gè)類,需要?jiǎng)?chuàng)建一個(gè)實(shí)例并觸發(fā)printPersons方法:
printPersons( roster, new CheckPersonEligibleForSelectiveService());
現(xiàn)在的代碼看起來(lái)不那么脆弱了——我們不需要因?yàn)镻erson類結(jié)構(gòu)的變化而重寫代碼。不過(guò)這里仍有額外的代碼:一個(gè)新定義的接口、為應(yīng)用中每個(gè)搜索標(biāo)準(zhǔn)定義了一個(gè)內(nèi)部類。
因?yàn)镃heckPersonEligibleForSelectiveService實(shí)現(xiàn)了一個(gè)接口,所以可以使用匿名類,而不需要再為每種標(biāo)準(zhǔn)分別定義一個(gè)內(nèi)部類。
方案四:使用匿名類實(shí)現(xiàn)標(biāo)準(zhǔn)檢查
下面調(diào)用的printPersons方法中的一個(gè)參數(shù)是匿名類。這個(gè)匿名類的作用和方案三中的CheckPersonEligibleForSelectiveService類一樣:都是過(guò)濾性別為男且年齡在18和25歲之間的會(huì)員。
printPersons( roster, new CheckPerson() { public boolean test(Person p) { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } } );
這個(gè)方案減少了編碼量,因?yàn)椴辉傩枰獮槊總€(gè)要執(zhí)行的檢索方案創(chuàng)建新類。不過(guò)這樣做仍讓人有些不舒服:盡管CheckPerson接口只有一個(gè)方法,實(shí)現(xiàn)的匿名類仍是有些冗長(zhǎng)笨重。此時(shí)可以使用Lambda表達(dá)式替換匿名類,下面會(huì)說(shuō)下如何使用Lambda表達(dá)式替換匿名類。
方案五:使用Lambda表達(dá)式實(shí)現(xiàn)標(biāo)準(zhǔn)檢查
CheckPerson接口是一個(gè)函數(shù)式接口。所謂的函數(shù)式接口就是指任何只包含一個(gè)抽象方法的接口。(一個(gè)函數(shù)式接口中也可以有多個(gè)default方法或靜態(tài)方法)。既然函數(shù)式接口中只有一個(gè)抽象方法,那么在實(shí)現(xiàn)這個(gè)函數(shù)式接口的方法的時(shí)候可以省略掉方法的方法名。為了實(shí)現(xiàn)這個(gè)想法,可以使用Lambda表達(dá)式替換匿名類表達(dá)式。在下面重寫的printPersons方法中,相關(guān)的代碼做了高亮處理:
printPersons( roster, (Person p) -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );
在這里還可以使用一個(gè)標(biāo)準(zhǔn)的函數(shù)接口來(lái)替換CheckPerson接口,從而進(jìn)一步簡(jiǎn)化代碼。
方案六:在Lambda表達(dá)式中使用標(biāo)準(zhǔn)函數(shù)式接口
再來(lái)看一下CheckPerson接口:
interface CheckPerson { boolean test(Person p); }
這是一個(gè)非常簡(jiǎn)單的接口。因?yàn)橹挥幸粋€(gè)抽象方法,所以它也是一個(gè)函數(shù)式接口。這個(gè)抽象方法只接受一個(gè)參數(shù)并返回一個(gè)boolean值。這個(gè)抽象接口太過(guò)簡(jiǎn)單了,以至于我們會(huì)考慮是否有必要在應(yīng)用中定義一個(gè)這樣的接口。此時(shí)可以考慮使用JDK定義的標(biāo)準(zhǔn)函數(shù)式接口,可以在java.util.function包下找到這些接口。
在這個(gè)例子中,我們就可以使用Predicate<T>接口來(lái)替換CheckPerson。在這個(gè)接口中有一個(gè)boolean test(T t)方法:
interface Predicate<t> { boolean test(T t); }
Predicate<T>接口是一個(gè)泛型接口。泛型類(或者是泛型接口)使用一對(duì)尖括號(hào)(<>)指定了一個(gè)或多個(gè)類型參數(shù)。在這個(gè)接口中只有一個(gè)類型參數(shù)。在使用具體類聲明或?qū)嵗粋€(gè)泛型類時(shí),就會(huì)獲得一個(gè)參數(shù)化類。比如說(shuō)參數(shù)化類Predicate<Person>就是這樣的:
interface Predicate<person> { boolean test(Person t); }
在這個(gè)參數(shù)化類中有一個(gè)與CheckPerson.boolean test(Person p)方法的參數(shù)和返回值都一致的方法。因此就可以同如下方法所演示的一樣使用Predicate<T>接口來(lái)替換CheckPerson接口:
public static void printPersonsWithPredicate( List<person> roster, Predicate<person> tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }
然后使用下面的代碼就可以像方案三中一樣篩選適齡服兵役的會(huì)員了:
printPersonsWithPredicate( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );
有沒(méi)有注意到,這里使用Predicate<Person>作為參數(shù)類型時(shí)并沒(méi)有顯式指定參數(shù)類型。這里并不是唯一適用lambda表達(dá)式的地方,下面的方案會(huì)介紹更多l(xiāng)ambda表達(dá)式的用法。
方案七:在整個(gè)應(yīng)用中使用lambda表達(dá)式
再來(lái)看一下printPersonsWithPredicate 方法,考慮是否可以在這里使用lambda表達(dá)式:
public static void printPersonsWithPredicate( List<person> roster, Predicate<person> tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }
在這個(gè)方法中使用Predicate實(shí)例tester檢查了roster中的每個(gè)Person實(shí)例。如果Person實(shí)例符合tester中定義的檢查標(biāo)準(zhǔn),將會(huì)觸發(fā)Person實(shí)例的printPerson方法。
除了觸發(fā)printPerson方法,滿足tester標(biāo)準(zhǔn)的Person實(shí)例還可以執(zhí)行其他的方法??梢钥紤]使用lambda表達(dá)式指定要執(zhí)行的方法(私以為這個(gè)特性很好,解決了java中方法不能作為對(duì)象傳遞的問(wèn)題)?,F(xiàn)在需要一個(gè)類似printPerson方法的lambda表達(dá)式——一個(gè)只需要一個(gè)參數(shù)且返回為void的lambda表達(dá)式。記住一點(diǎn):要使用lambda表達(dá)式,需要先實(shí)現(xiàn)一個(gè)函數(shù)式接口。在這個(gè)例子中需要一個(gè)函數(shù)式接口,其中只包含一個(gè)抽象方法,這個(gè)抽象方法有個(gè)類型為Person的參數(shù),且返回為void??梢钥匆幌翵DK提供的標(biāo)準(zhǔn)函數(shù)式接口Consumer<T>,它有一個(gè)抽象方法void accept(T t)正好滿足這個(gè)要求。在下面的代碼中使用一個(gè)Consumer<T>的實(shí)例調(diào)用accept方法替換了p.printPerson():
public static void processPersons( List<person> roster, Predicate<person> tester, Consumer<person> block) { for (Person p : roster) { if (tester.test(p)) { block.accept(p); } } }
對(duì)應(yīng)這里,可以使用如下代碼篩選適齡服兵役的會(huì)員:
processPersons( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.printPerson() );
如果我們想做的事情不只是打印會(huì)員信息,而是更多的事情,比如驗(yàn)證會(huì)員身份、獲取會(huì)員聯(lián)系方式等等。此時(shí),我們需要一個(gè)有返回值方法的函數(shù)式接口。JDK的標(biāo)準(zhǔn)函數(shù)式接口Function<T,R>就有一個(gè)這樣的方法R apply(T t)。下面的方法從參數(shù)mapper中獲取數(shù)據(jù),并在這些數(shù)據(jù)上執(zhí)行參數(shù)block指定的行為:
public static void processPersonsWithFunction( List<person> roster, Predicate<person> tester, Function<person , string> mapper, Consumer<string> block) { for (Person p : roster) { if (tester.test(p)) { String data = mapper.apply(p); block.accept(data); } } }
下面的代碼獲取了roster中適齡服兵役的所有會(huì)員的郵箱信息并打印出來(lái):
processPersonsWithFunction( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) );
方案八:多使用泛型
再來(lái)回顧一下processPersonsWithFunction方法。下面是這個(gè)方法的泛型版本,新方法在參數(shù)類型上要求更為寬容:
public static <x , Y> void processElements( Iterable<x> source, Predicate<x> tester, Function<x , Y> mapper, Consumer<y> block) { for (X p : source) { if (tester.test(p)) { Y data = mapper.apply(p); block.accept(data); } } }
要打印適齡服兵役的會(huì)員信息可以像下面這樣調(diào)用processElements方法:
processElements( roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), email -> System.out.println(email) );
在方法的調(diào)用過(guò)程中執(zhí)行了如下行為:
從一個(gè)集合中獲取對(duì)象信息,在這個(gè)例子里是從集合實(shí)例roster中獲取Person對(duì)象信息。
過(guò)濾能夠匹配Predicate實(shí)例tester的對(duì)象。在這個(gè)例子里,Predicate對(duì)象是一個(gè)lambda表達(dá)式,它指定了過(guò)濾適齡服兵役的條件。
將過(guò)濾后的對(duì)象交給一個(gè)Function對(duì)象mapper處理,mapper會(huì)為這個(gè)對(duì)象匹配一個(gè)值。在這個(gè)例子中Function對(duì)象mapper是一個(gè)lambda表達(dá)式,它返回了每個(gè)會(huì)員的郵箱地址。
由Consumer對(duì)象block為mapper匹配的值指定一個(gè)行為。在這個(gè)例子里,Consumer對(duì)象是一個(gè)lambda表達(dá)式,它的作用是打印一個(gè)字符串,也就是Function實(shí)例mapper返回的會(huì)員郵件地址。
方案九:使用將lambda表達(dá)式作為參數(shù)的聚集操作
下面的代碼中使用了聚集操作來(lái)打印roster集合中適齡服兵役會(huì)員的郵件地址:
roster.stream() .filter( p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25) .map(p -> p.getEmailAddress()) .forEach(email -> System.out.println(email));
分析下如上代碼的執(zhí)行過(guò)程,整理如下表:
行為 |
聚集操作 |
獲取對(duì)象 |
Stream<E> stream() |
過(guò)濾匹配Predicate實(shí)例指定標(biāo)準(zhǔn)的對(duì)象 |
Stream<T> filter(Predicate<? super T> predicate) |
通過(guò)一個(gè)Function實(shí)例獲取對(duì)象匹配的值 |
<R> Stream<R> map(Function<? super T,? extends R> mapper) |
執(zhí)行Consumer實(shí)例指定的行為 |
void forEach(Consumer<? super T> action) |
表中的filter、map和forEach操作都是聚集操作。聚集操作處理的元素來(lái)自Stream,而非是直接從集合中獲?。ň褪且?yàn)檫@示例程序中調(diào)用的第一個(gè)方法是stream())。Stream是一個(gè)數(shù)據(jù)序列。和集合不同,Stream并沒(méi)有用特定的結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)。相反的,Stream從一個(gè)特定的源獲取數(shù)據(jù),比如從集合獲取數(shù)據(jù),通過(guò)一個(gè)pipeline。pipeline是一個(gè)Stream操作序列,在這個(gè)例子中就是filter-map-forEach。此外,聚集操作通常采用lambda表達(dá)式作為參數(shù),這也給了我們?cè)S多自定義的空間。
相關(guān)文章
feign的ribbon超時(shí)配置和hystrix的超時(shí)配置說(shuō)明
這篇文章主要介紹了feign的ribbon超時(shí)配置和hystrix的超時(shí)配置說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09如何利用JConsole觀察分析Java程序的運(yùn)行并進(jìn)行排錯(cuò)調(diào)優(yōu)
從Java 5開始 引入了 JConsole。JConsole 是一個(gè)內(nèi)置 Java 性能分析器,可以從命令行或在 GUI shell 中運(yùn)行。您可以輕松地使用 JConsole(或者,它更高端的 “近親” VisualVM )來(lái)監(jiān)控 Java 應(yīng)用程序性能和跟蹤 Java 中的代碼2015-12-12JavaWeb 簡(jiǎn)單分頁(yè)實(shí)現(xiàn)代碼
這篇文章主要介紹了JavaWeb 簡(jiǎn)單分頁(yè)實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-11-11使用CXF和Jersey框架來(lái)進(jìn)行Java的WebService編程
這篇文章主要介紹了使用CXF和Jersey框架來(lái)進(jìn)行Java的WebService編程,Web service是一個(gè)平臺(tái)獨(dú)立的低耦合的自包含的基于可編程的web的應(yīng)用程序,需要的朋友可以參考下2015-12-12