系统学习Lambda表达式
1. 語(yǔ)法
首先我們要知道如何寫Lambda表達(dá)式,或者說(shuō)怎么樣才能寫出有效的Lambda表達(dá)式,這就需要了解其語(yǔ)法。
Lambda表達(dá)式由三部分組成:
參數(shù)列表
箭頭
主體
有兩種風(fēng)格,分別是:
表達(dá)式-風(fēng)格
(parameters) -> expression
塊-風(fēng)格
(parameters) -> { statements; }
依據(jù)上面的風(fēng)格介紹,來(lái)試著判斷下面給出的示例是否有效:
() -> {}() -> "Apple"() -> { return "Apple"; }(Integer i) -> return "Apple" + i(String s) -> { "Apple"; }解析:(1)是塊風(fēng)格,沒(méi)有語(yǔ)句;(2)是表達(dá)式風(fēng)格,一個(gè)字符串表達(dá)式;(3)是塊風(fēng)格,有花括號(hào)和返回語(yǔ)句;(4)非有效,寫了返回語(yǔ)句,但缺少花括號(hào),補(bǔ)上花括號(hào)和分號(hào),為塊風(fēng)格,而去掉return則為表達(dá)式風(fēng)格;(5)非有效,"Apple"是一個(gè)字符串表達(dá)式,不是一個(gè)語(yǔ)句,加上return,或者去掉分號(hào)和花括號(hào)。
?
2. 函數(shù)式接口
Lambda表達(dá)式寫好了,我們要知道哪里能用Lambda表達(dá)式。已知Lambda表達(dá)式可看作是匿名內(nèi)部類的實(shí)現(xiàn),那對(duì)于匿名內(nèi)部類來(lái)說(shuō)最重要的是類所實(shí)現(xiàn)的接口,而Lambda表達(dá)式是否可用于所有接口?答案“不是的”,Lambda表達(dá)式對(duì)接口有一定的要求,必須是函數(shù)式接口。
所謂的函數(shù)式接口指的是只定義一個(gè)抽象方法的接口。
例如:
public?interface?Comparator<T>?{int?compare(T?o1,?T?o2); }public?interface?Runnable?{void?run(); }public?interface?Callable<V>?{V?call()?throws?Exception; }可見(jiàn)上面三個(gè)接口都只有一個(gè)抽象方法,但是三個(gè)方法的簽名都不一樣,這要求Lambda表達(dá)式與實(shí)現(xiàn)接口的方法簽名要一致。下面用函數(shù)描述符來(lái)表示上述三個(gè)方法的簽名,箭頭前面是方法的入?yún)㈩愋?#xff0c;后面是返回類型。
compare:(T, T) -> int,兩個(gè)泛型T類型的入?yún)?#xff0c;返回int類型
Lambda表達(dá)式:(Apple a1, Apple a2) -> a1.getWeight - a2.getWeight
run:() -> void,無(wú)入?yún)?#xff0c;無(wú)返回值
Lambda表達(dá)式:() -> { System.out.println("Hi"); }
call:() -> V,無(wú)入?yún)?#xff0c;返回一個(gè)泛型V類型的對(duì)象
Lambda表達(dá)式:() -> new Apple()
看call方法的示例,你是否會(huì)疑惑,new Apple()是一個(gè)語(yǔ)句,為什么沒(méi)有花括號(hào)和分號(hào),是不是非有效的。你需要記住這是合法的,這是一個(gè)特殊的規(guī)定,不需要用括號(hào)環(huán)繞返回值為void的單行方法調(diào)用。
?
3. 常用的函數(shù)式接口
下面介紹在Java中內(nèi)置的常用Lambda表達(dá)式:
3.1 Predicate
public?interface?Predicate<T>?{boolean?test(T?t); }test:T -> boolean,接收一個(gè)泛型T對(duì)象,返回一個(gè)boolean。
適用場(chǎng)景:表示一個(gè)涉及類型T的布爾表達(dá)式。
//?判斷空白字符串 Predicate<String>?blankStrPredicate?=?s?->?s?!=?null?&&?s.trim().length()?==?0; blankStrPredicate.test("??");?//?true //?判斷蘋果重量是否大于150 Predicate<Apple>?heavyApplePredicate?=?a?->?a.getWeight()?>?150; heavyApplePredicate.test(new?Apple(100));?//?false注意,參數(shù)部分缺少了參數(shù)類型,是因?yàn)榭筛鶕?jù)上下文推斷出Lambda表達(dá)式的參數(shù)類型,所以可以省略不寫。比如這里因?yàn)閷ambda表達(dá)式賦值給一個(gè)Predicate類型的變量,又因?yàn)楹瘮?shù)描述符為(T) -> boolean,則可推斷出參數(shù)T的實(shí)際類型為String。而且當(dāng)只有一個(gè)參數(shù)時(shí),可以將括號(hào)也省略。
3.2 Consumer
public?interface?Consumer<T>?{void?accept(T?t); }accept:T -> void,接收一個(gè)泛型T對(duì)象,無(wú)返回值(void)。
適用場(chǎng)景:訪問(wèn)類型T的對(duì)象,對(duì)其執(zhí)行某些操作。
//?打印蘋果重量 Consumer<Apple>?appleWeighter?=?a?->?System.out.println("The?apple?weights?"?+?a.getWeight()?+?"?grams"); appleWeighter.accept(new?Apple(200));? //?The?apple?weights?200?grams3.3 Supplier
public?interface?Supplier<T>?{T?get(); }get:() -> T,無(wú)入?yún)?#xff0c;返回一個(gè)泛型T對(duì)象。
適用場(chǎng)景:定義類型T的對(duì)象的生產(chǎn)規(guī)則。
Consumer<Apple>?appleWeighter?=(a)?->?System.out.println("The?apple?weights?"?+?a.getWeight()?+?"?grams"); //?生產(chǎn)200克的蘋果 Supplier<Apple>?heavyAppleSupplier?=?()?->?new?Apple(200); appleWeighter.accept(heavyAppleSupplier.get());3.4 Function
public?interface?Function<T,?R>?{R?apply(T?t); }apply:T -> R,接受一個(gè)泛型T對(duì)象,返回一個(gè)泛型R對(duì)象
適用場(chǎng)景:將輸入對(duì)象轉(zhuǎn)換輸出。
double?unitPrice?=?0.01; //?計(jì)算蘋果價(jià)格 Function<Apple,?Double>?priceAppleFunction?=?a?->?a.getWeight()?*?unitPrice; priceAppleFunction.apply(new?Apple(100));?//?1這里做個(gè)補(bǔ)充,上面這段代碼特別的地方在于使用到了外部的局部變量。Lambda表達(dá)式使用外部變量有什么要求?對(duì)于Lambda表達(dá)式所在的主體(類)的實(shí)例變量和靜態(tài)變量,可以無(wú)限制使用,但局部變量必須顯示聲明為final或?qū)嶋H上是final的。
聲明為final好理解,什么是實(shí)際上是final的,意思就是不能被代碼進(jìn)行修改,比如這里的unitPrice雖然沒(méi)有聲明為final,但后續(xù)的代碼并沒(méi)有修改該變量,所以實(shí)際上也是final的。感興趣的讀者可以自己試下,對(duì)unitPrice進(jìn)行修改,看下會(huì)發(fā)生什么。
3.5 Comparator
public?interface?Comparator<T>?{int?compare(T?o1,?T?o2); }compare:(T, T) -> int,兩個(gè)泛型T類型的入?yún)?#xff0c;返回int類型
適用場(chǎng)景:比較兩個(gè)對(duì)象
Comparator<Apple>?weightComparator?=?(a1,?a2)?->?a1.getWeight()?-?a2.getWeight(); weightComparator.compare(new?Apple(100),?new?Apple(150));?//?-1?
4. 方法引用
Java還提供了一種更簡(jiǎn)潔的寫法,先上示例:
Function<Apple,?Integer>?weightor?=?a?->?a.getWeight();可改寫為:
Function<Apple,?Integer>?weightor?=?Apple::getWeight;這種寫法被稱作方法引用,僅當(dāng)在Lambda表達(dá)式中直接調(diào)用了一個(gè)方法時(shí)可以使用。其寫法為目標(biāo)引用::方法名稱。
根據(jù)目標(biāo)引用可分為三類:
(1)指向靜態(tài)方法的方法引用
目標(biāo)引用為類,調(diào)用其靜態(tài)方法,例如:
Function<String,?Integer>?fun?=?s?->?Integer.parseInt(s);調(diào)用了?Integer?的靜態(tài)方法?parseInt,可寫為:
Function<String,?Integer>?fun?=?Integer::parseInt;(2)指向任意類型實(shí)例方法的方法引用
目標(biāo)引用為實(shí)例對(duì)象,調(diào)用其實(shí)例方法,例如:
Function<String,?Integer>?fun?=?s?->?s.length();調(diào)用了?String?類型實(shí)例?s?的?length?方法,可寫為:
Function<String,?Integer>?fun?=?String::length;目標(biāo)引用寫實(shí)例的類型。和第一種寫法相同,只不過(guò)第一種的方法是靜態(tài)方法,這里是實(shí)例方法。
(3)指向現(xiàn)存外部對(duì)象實(shí)例方法的方法引用
目標(biāo)引用為現(xiàn)存外部對(duì)象,調(diào)用其實(shí)例方法,例如:
String?s?=?"草捏子"; Supplier<Integer>?len?=?()?->?s.length();調(diào)用了局部變量?s?的?length?方法,可寫為:
String?s?=?"草捏子"; Supplier<Integer>?len?=?s::length;目標(biāo)引用寫變量名,區(qū)別于前兩種。
?
5. 復(fù)合Lambda表達(dá)式
之前的例子都是使用的單個(gè)Lambda表達(dá)式,現(xiàn)在我們把多個(gè)Lambda表達(dá)式組合在一起,構(gòu)建更復(fù)雜一點(diǎn)的表達(dá)式。
5.1 比較器復(fù)合(Comparator)
我們使用?Comparator?對(duì)蘋果進(jìn)行排序,按重量從小到大:
List<Apple>?apples?=?Arrays.asList(new?Apple("red",?50),?new?Apple("red",?100),?new?Apple("green",?100)); apples.sort(Comparator.comparing(Apple::getWeight));對(duì)?Comparator?的靜態(tài)方法comparing?簡(jiǎn)單介紹下,接受一個(gè)?Function?類型的參數(shù),返回一個(gè)?Comparator?類型的實(shí)例,定義如下:
public?static?<T,?U?extends?Comparable<??super?U>>?Comparator<T>?comparing(Function<??super?T,???extends?U>?keyExtractor) {Objects.requireNonNull(keyExtractor);return?(Comparator<T>?&?Serializable)(c1,?c2)?->?keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); }通過(guò)使用compareTo,實(shí)現(xiàn)了重量從小到大的排序,那想按重量從大到小排序,怎么辦呢?可以使用?Comparator?的?reversed?方法:
apples.sort(Comparator.comparing(Apple::getWeight).reversed());reversed?的實(shí)現(xiàn)如下:
default?Comparator<T>?reversed()?{return?Collections.reverseOrder(this); }使用工具類得到一個(gè)反序的?Comparator。你可能會(huì)好奇Comparator?作為一個(gè)接口,reversed?方法可以有具體的實(shí)現(xiàn),接口的實(shí)例方法應(yīng)該都是抽象方法,那它還是一個(gè)有效的函數(shù)式接口嗎,或者說(shuō)還是一個(gè)有效的接口嗎?
回想下第二節(jié)的內(nèi)容,函數(shù)式接口是只定義一個(gè)抽象方法的接口。Comparator的抽象方法只有一個(gè)?compare,其他是具體方法,所以是合法的函數(shù)式接口。那么接口中為什么能定義具體方法呢?Java8 之前是不支持的,但在 Java8 中引入了?default?關(guān)鍵字。
當(dāng)在接口中用default聲明一個(gè)方法時(shí),允許它是一個(gè)具體方法。這樣的好處在于,我們可以在Lambda表達(dá)式之后直接跟上一個(gè)具體方法,對(duì)Lambda表達(dá)式增強(qiáng),實(shí)現(xiàn)更復(fù)雜的功能。在后文介紹的用于復(fù)合表達(dá)式的方法都是接口中的?default?方法。
下面我們?cè)囍鴮?shí)現(xiàn)更復(fù)雜的排序,在按重量從大到小排序后,按顏色排序:
apples.sort(Comparator.comparing(Apple::getWeight).reversed()); apples.sort(Comparator.comparing(Apple::getColor));先后用兩個(gè)Comparator。而使用?Comparator?的?thenComparing?方法可以繼續(xù)連接一個(gè)?Comparator,從而構(gòu)建更復(fù)雜的排序:
apples.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));5.2 謂詞復(fù)合(Predicate)
Predicate?的?test?方法?(T) -> boolean返回一個(gè)布爾表達(dá)式。類似 Java 在為布爾表達(dá)式提供的與或非,Predicate中也有對(duì)應(yīng)的方法?and、or、negate。例如:
//?重的蘋果 Predicate<Apple>?heavyApple?=?a?->?a.getWeight()?>?100; //?紅的蘋果 Predicate<Apple>?redApple?=?a?->?a.getColor().equals("red");//?輕的蘋果 Predicate<Apple>?lightApple?=?heavyApple.negate(); //?不紅的蘋果 Predicate<Apple>?nonRedApple?=?redApple.negate(); //?重且紅的蘋果 Predicate<Apple>?heavyAndRedApple?=?heavyApple.and(redApple); //?重或紅的蘋果 Predicate<Apple>?heavyOrRedApple?=?heavyApple.or(redApple);5.3 函數(shù)復(fù)合(Function)
Function?(T) -> R,對(duì)輸入做映射。我們通過(guò)將多個(gè)Function進(jìn)行組合,實(shí)現(xiàn)將一個(gè)Function的輸出作為另一個(gè)Function的輸入,是不是有管道的感覺(jué)。下面請(qǐng)看具體的方法。
andThen方法,a.andThen(b),將先執(zhí)行a,再執(zhí)行b。
Function<Integer,?Integer>?f?=?x?->?x?+?1; Function<Integer,?Integer>?g?=?x?->?x?*?2; Function<Integer,?Integer>?h?=?f.andThen(g); int?result?=?h.apply(1);?//?4compose方法,a.compose(b),將先執(zhí)行b,再執(zhí)行a。
Function<Integer,?Integer>?f?=?x?->?x?+?1; Function<Integer,?Integer>?g?=?x?->?x?*?2; Function<Integer,?Integer>?h?=?f.compose(g); int?result?=?h.apply(1);?//?3?
總結(jié)
以上是生活随笔為你收集整理的系统学习Lambda表达式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 艿艿连肝了几个周末,写了一篇贼长的 Sp
- 下一篇: 和6岁孩子的函数式编程对话