原來(lái)?Lamda?表達(dá)式是這樣寫的
時(shí)間:2021-11-05 14:04:18
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
[導(dǎo)讀]Lamda表達(dá)式非常方便,在項(xiàng)目中一般在stream編程中用的比較多。List?studentList?=?gen();Map?map?=?studentList?.stream()????????.collect(Collectors.toMap(Student::getId,...
Lamda 表達(dá)式非常方便,在項(xiàng)目中一般在 stream 編程中用的比較多。
1. 確認(rèn) Lamda 表達(dá)式的類型2. 找到要實(shí)現(xiàn)的方法3. 實(shí)現(xiàn)這個(gè)方法?就這三步,沒(méi)其他的了。而每一步,都非常非常簡(jiǎn)單,以至于我分別展開(kāi)講一下,你就懂了。?
能用 Lamda 表達(dá)式來(lái)表示的類型,必須是一個(gè)函數(shù)式接口,而函數(shù)式接口,就是只有一個(gè)抽象方法的接口。?我們看下非常熟悉的 Runnable 接口在 JDK 中的樣子就明白了。
因?yàn)橹挥幸粋€(gè)抽象方法。而且這個(gè)接口上有個(gè)注解
@FunctionalInterface這個(gè)僅僅是在編譯期幫你檢查你這個(gè)接口是否符合函數(shù)式接口的條件,比如你沒(méi)有任何抽象方法,或者有多個(gè)抽象方法,編譯是無(wú)法通過(guò)的。
?看看你可能不太熟悉又有點(diǎn)似曾相識(shí)的一個(gè)接口。
?恭喜你,已經(jīng)學(xué)會(huì)了 Lamda 表達(dá)式最難的部分,就是認(rèn)識(shí)函數(shù)式接口。?
?Lamda 表達(dá)式就是實(shí)現(xiàn)一個(gè)方法,什么方法呢?就是剛剛那些函數(shù)式接口中的抽象方法。?那就太簡(jiǎn)單了,因?yàn)楹瘮?shù)式接口有且只有一個(gè)抽象方法,找到它就行了。我們嘗試把剛剛那幾個(gè)函數(shù)式接口的抽象方法找到。
?
?Lamda 表達(dá)式就是要實(shí)現(xiàn)這個(gè)抽象方法,如果不用 Lamda 表達(dá)式,你一定知道用匿名類如何去實(shí)現(xiàn)吧?比如我們實(shí)現(xiàn)剛剛 Predicate 接口的匿名類。
參數(shù)塊:就是前面的 (String s),就是簡(jiǎn)單地把要實(shí)現(xiàn)的抽象方法的參數(shù)原封不動(dòng)寫在這。小箭頭:就是 -> 這個(gè)符號(hào)。代碼塊:就是要實(shí)現(xiàn)的方法原封不動(dòng)寫在這。?不過(guò)這樣的寫法你一定不熟悉,連 idea 都不會(huì)幫我們簡(jiǎn)化成這個(gè)奇奇怪怪的樣子,別急,我要變形了!其實(shí)是對(duì)其進(jìn)行格式上的簡(jiǎn)化。?首先看參數(shù)快部分,(String s) 里面的類型信息是多余的,因?yàn)橥耆梢杂删幾g器推導(dǎo),去掉它。
來(lái),再讓我們實(shí)現(xiàn)一個(gè) Runnable 接口。
之前我們只嘗試了一個(gè)入?yún)?,接下?lái)我們看看多個(gè)入?yún)⒌摹?/span>
OK,看了這么多例子,不知道你發(fā)現(xiàn)規(guī)律了沒(méi)??其實(shí)函數(shù)式接口里那個(gè)抽象方法,無(wú)非就是入?yún)⒌膫€(gè)數(shù),以及返回值的類型。入?yún)⒌膫€(gè)數(shù)可以是一個(gè)或者兩個(gè),返回值可以是 void,或者 boolean,或者一個(gè)類型。那這些種情況的排列組合,就是 JDK 給我們提供的java.util.function包下的類。?BiConsumerBiFunctionBinaryOperatorBiPredicateBooleanSupplierConsumerDoubleBinaryOperatorDoubleConsumerDoubleFunctionDoublePredicateDoubleSupplierDoubleToIntFunctionDoubleToLongFunctionDoubleUnaryOperatorFunctionIntBinaryOperatorIntConsumerIntFunctionIntPredicateIntSupplierIntToDoubleFunctionIntToLongFunctionIntUnaryOperatorLongBinaryOperatorLongConsumerLongFunctionLongPredicateLongSupplierLongToDoubleFunctionLongToIntFunctionLongUnaryOperatorObjDoubleConsumerObjIntConsumerObjLongConsumerPredicateSupplierToDoubleBiFunctionToDoubleFunctionToIntBiFunctionToIntFunctionToLongBiFunctionToLongFunctionUnaryOperator?別看暈了,我們分分類就好了??梢宰⒁獾胶芏囝惽熬Y是 Int,Long,Double 之類的,這其實(shí)是指定了入?yún)⒌奶囟愋停辉偈且粋€(gè)可以由用戶自定義的泛型,比如說(shuō) DoubleFunction。
?supplier:沒(méi)有入?yún)?,有返回值?/span>consumer:有入?yún)?,無(wú)返回值。predicate:有入?yún)?,返?boolean 值function:有入?yún)?,有返回?/span>?然后帶 Bi 前綴的,就是有兩個(gè)入?yún)?,不帶的就只有一個(gè)如參。OK,這些已經(jīng)被我們分的一清二楚了,其實(shí)就是給我們提供了一個(gè)函數(shù)的模板,區(qū)別僅僅是入?yún)⒎祬€(gè)數(shù)的排列組合。?
List?studentList?=?gen();
Map?map?=?studentList?.stream()
????????.collect(Collectors.toMap(Student::getId,?a?->?a,?(a,?b)?->?a));
理解一個(gè) Lamda 表達(dá)式就三步:1. 確認(rèn) Lamda 表達(dá)式的類型2. 找到要實(shí)現(xiàn)的方法3. 實(shí)現(xiàn)這個(gè)方法?就這三步,沒(méi)其他的了。而每一步,都非常非常簡(jiǎn)單,以至于我分別展開(kāi)講一下,你就懂了。?
確認(rèn) Lamda 表達(dá)式的類型
?能用 Lamda 表達(dá)式來(lái)表示的類型,必須是一個(gè)函數(shù)式接口,而函數(shù)式接口,就是只有一個(gè)抽象方法的接口。?我們看下非常熟悉的 Runnable 接口在 JDK 中的樣子就明白了。
@FunctionalInterface
public?interface?Runnable?{
????public?abstract?void?run();
}
這就是一個(gè)標(biāo)準(zhǔn)的函數(shù)式接口。因?yàn)橹挥幸粋€(gè)抽象方法。而且這個(gè)接口上有個(gè)注解
@FunctionalInterface這個(gè)僅僅是在編譯期幫你檢查你這個(gè)接口是否符合函數(shù)式接口的條件,比如你沒(méi)有任何抽象方法,或者有多個(gè)抽象方法,編譯是無(wú)法通過(guò)的。
//?沒(méi)有實(shí)現(xiàn)任何抽象方法的接口
@FunctionalInterface
public?interface?MyRunnable?{}
//?編譯后控制臺(tái)顯示如下信息
Error:(3,?1)?java:?
??意外的?@FunctionalInterface?注釋
??MyRunnable?不是函數(shù)接口
????在?接口?MyRunnable?中找不到抽象方法
再稍稍復(fù)雜一點(diǎn),Java 8 之后接口中是允許使用默認(rèn)方法和靜態(tài)方法的,而這些都不算抽象方法,所以也可以加在函數(shù)式接口里。?看看你可能不太熟悉又有點(diǎn)似曾相識(shí)的一個(gè)接口。
@FunctionalInterface
public?interface?Consumer<T>?{
????void?accept(T?t);
????default?Consumer?andThen(Consumer?super?T>?after)? {...}
}
看,只有一個(gè)抽象方法,還有一個(gè)默認(rèn)方法(方法體的代碼省略了),這個(gè)也不影響它是個(gè)函數(shù)式接口。再看一個(gè)更復(fù)雜的,多了靜態(tài)方法,這同樣也是個(gè)函數(shù)式接口,因?yàn)樗匀恢挥幸粋€(gè)抽象方法。自行體會(huì)吧。@FunctionalInterface
public?interface?Predicate<T>?{
????boolean?test(T?t);
????
????default?Predicate?and(Predicate?super?T>?other)? {...}
????default?Predicate?negate()? {...}
????default?Predicate?or(Predicate?super?T>?other)? {...}
????
????static??Predicate?isEqual(Object?targetRef)? {...}
????static??Predicate?not(Predicate?super?T>?target)? {...}
}
先不用管這些方法都是干嘛的,這些類在 Stream 設(shè)計(jì)的方法中比比皆是,我們就先記住這么一句話,Lamda 表達(dá)式需要的類型為函數(shù)式接口,函數(shù)式接口里只有一個(gè)抽象方法,就夠了,以上三個(gè)例子都屬于函數(shù)式接口。?恭喜你,已經(jīng)學(xué)會(huì)了 Lamda 表達(dá)式最難的部分,就是認(rèn)識(shí)函數(shù)式接口。?
找到要實(shí)現(xiàn)的方法
?Lamda 表達(dá)式就是實(shí)現(xiàn)一個(gè)方法,什么方法呢?就是剛剛那些函數(shù)式接口中的抽象方法。?那就太簡(jiǎn)單了,因?yàn)楹瘮?shù)式接口有且只有一個(gè)抽象方法,找到它就行了。我們嘗試把剛剛那幾個(gè)函數(shù)式接口的抽象方法找到。
@FunctionalInterface
public?interface?Runnable?{
????public?abstract?void?run();
}
@FunctionalInterface
public?interface?Consumer<T>?{
????void?accept(T?t);
????default?Consumer?andThen(Consumer?super?T>?after)? {...}
}
@FunctionalInterface
public?interface?Predicate<T>?{
????boolean?test(T?t);
????default?Predicate?and(Predicate?super?T>?other)? {...}
????default?Predicate?negate()? {...}
????default?Predicate?or(Predicate?super?T>?other)? {...}
????static??Predicate?isEqual(Object?targetRef)? {...}
????static??Predicate?not(Predicate?super?T>?target)? {...}
}
好了,這就找到了,簡(jiǎn)單吧!?
實(shí)現(xiàn)這個(gè)方法
?Lamda 表達(dá)式就是要實(shí)現(xiàn)這個(gè)抽象方法,如果不用 Lamda 表達(dá)式,你一定知道用匿名類如何去實(shí)現(xiàn)吧?比如我們實(shí)現(xiàn)剛剛 Predicate 接口的匿名類。
Predicate?predicate?=?new?Predicate()?{
????@Override
????public?boolean?test(String?s)?{
????????return?s.length()?!=?0;
????}
};
那如果換成 Lamda 表達(dá)式呢?就像這樣。Predicate?predicate?=?
????(String?s)?->?{
????????return?s.length()?!=?0;
????};
?看出來(lái)了么?這個(gè) Lamda 語(yǔ)法由三部分組成:參數(shù)塊:就是前面的 (String s),就是簡(jiǎn)單地把要實(shí)現(xiàn)的抽象方法的參數(shù)原封不動(dòng)寫在這。小箭頭:就是 -> 這個(gè)符號(hào)。代碼塊:就是要實(shí)現(xiàn)的方法原封不動(dòng)寫在這。?不過(guò)這樣的寫法你一定不熟悉,連 idea 都不會(huì)幫我們簡(jiǎn)化成這個(gè)奇奇怪怪的樣子,別急,我要變形了!其實(shí)是對(duì)其進(jìn)行格式上的簡(jiǎn)化。?首先看參數(shù)快部分,(String s) 里面的類型信息是多余的,因?yàn)橥耆梢杂删幾g器推導(dǎo),去掉它。
Predicate?predicate?=?
????(s)?->?{
????????return?s.length()?!=?0;
????};
?當(dāng)只有一個(gè)參數(shù)時(shí),括號(hào)也可以去掉。Predicate?predicate?=?
????s?->?{
????????return?s.length()?!=?0;
????};
再看代碼塊部分,方法體中只有一行代碼,可以把花括號(hào)和 return 關(guān)鍵字都去掉。Predicate?p?=?s?->?s.length()?!=?0;
這樣看是不是就熟悉點(diǎn)了?來(lái),再讓我們實(shí)現(xiàn)一個(gè) Runnable 接口。
@FunctionalInterface
public?interface?Runnable?{
????public?abstract?void?run();
}
Runnable?r?=?()?->?System.out.println("I?am?running");
你看,這個(gè)方法沒(méi)有入?yún)?,所以前面括?hào)里的參數(shù)就沒(méi)有了,這種情況下括號(hào)就不能省略。?通常我們快速新建一個(gè)線程并啟動(dòng)時(shí),是不是像如下的寫法,熟悉吧?new?Thread(()?->?System.out.println("I?am?running")).start();
?多個(gè)入?yún)?/span>
?之前我們只嘗試了一個(gè)入?yún)?,接下?lái)我們看看多個(gè)入?yún)⒌摹?/span>
@FunctionalInterface
public?interface?BiConsumer<T,?U>?{
????void?accept(T?t,?U?u);
????//?default?methods?removed
}
然后看看一個(gè)用法,是不是一目了然。BiConsumer?randomNumberPrinter?=?
????????(random,?number)?->?{
????????????for?(int?i?=?0;?i?????????????????System.out.println("next?random?=?"? ?random.nextInt());
????????????}
????????};
????????
randomNumberPrinter.accept(new?Random(314L),?5));
剛剛只是多個(gè)入?yún)ⅲ俏覀冊(cè)偌觽€(gè)返回值。@FunctionalInterface
public?interface?BiFunction<T,?U,?R>?{
????R?apply(T?t,?U?u);
????//?default?methods?removed
}
//?看個(gè)例子
BiFunction?findWordInSentence?=?
????(word,?sentence)?->?sentence.indexOf(word);
發(fā)現(xiàn)規(guī)律了沒(méi)
?OK,看了這么多例子,不知道你發(fā)現(xiàn)規(guī)律了沒(méi)??其實(shí)函數(shù)式接口里那個(gè)抽象方法,無(wú)非就是入?yún)⒌膫€(gè)數(shù),以及返回值的類型。入?yún)⒌膫€(gè)數(shù)可以是一個(gè)或者兩個(gè),返回值可以是 void,或者 boolean,或者一個(gè)類型。那這些種情況的排列組合,就是 JDK 給我們提供的java.util.function包下的類。?BiConsumerBiFunctionBinaryOperatorBiPredicateBooleanSupplierConsumerDoubleBinaryOperatorDoubleConsumerDoubleFunctionDoublePredicateDoubleSupplierDoubleToIntFunctionDoubleToLongFunctionDoubleUnaryOperatorFunctionIntBinaryOperatorIntConsumerIntFunctionIntPredicateIntSupplierIntToDoubleFunctionIntToLongFunctionIntUnaryOperatorLongBinaryOperatorLongConsumerLongFunctionLongPredicateLongSupplierLongToDoubleFunctionLongToIntFunctionLongUnaryOperatorObjDoubleConsumerObjIntConsumerObjLongConsumerPredicateSupplierToDoubleBiFunctionToDoubleFunctionToIntBiFunctionToIntFunctionToLongBiFunctionToLongFunctionUnaryOperator?別看暈了,我們分分類就好了??梢宰⒁獾胶芏囝惽熬Y是 Int,Long,Double 之類的,這其實(shí)是指定了入?yún)⒌奶囟愋停辉偈且粋€(gè)可以由用戶自定義的泛型,比如說(shuō) DoubleFunction。
@FunctionalInterface
public?interface?DoubleFunction<R>?{
????R?apply(double?value);
}
這完全可以由更自由的函數(shù)式接口 Function 來(lái)實(shí)現(xiàn)。@FunctionalInterface
public?interface?Function<T,?R>?{
????R?apply(T?t);
}
那我們不妨先把這些特定類型的函數(shù)式接口去掉(我還偷偷去掉了 XXXOperator 的幾個(gè)類,因?yàn)樗鼈兌际抢^承了別的函數(shù)式接口),然后再排排序,看看還剩點(diǎn)啥。?ConsumerFunctionPredicate?BiConsumerBiFunctionBiPredicate?Supplier?哇塞,幾乎全沒(méi)了,接下來(lái)就重點(diǎn)看看這些。這里我就只把類和對(duì)應(yīng)的抽象方法列舉出來(lái)?Consumer??void accept(T t)Function?R apply(T t)Predicate? ?boolean test(T t)?BiConsumer?void accept(T t, U u)BiFunction?R apply(T t, U u)BiPredicate?boolean test(T t, U u)?Supplier?T get()?看出規(guī)律了沒(méi)?上面那幾個(gè)簡(jiǎn)單分類就是:?supplier:沒(méi)有入?yún)?,有返回值?/span>consumer:有入?yún)?,無(wú)返回值。predicate:有入?yún)?,返?boolean 值function:有入?yún)?,有返回?/span>?然后帶 Bi 前綴的,就是有兩個(gè)入?yún)?,不帶的就只有一個(gè)如參。OK,這些已經(jīng)被我們分的一清二楚了,其實(shí)就是給我們提供了一個(gè)函數(shù)的模板,區(qū)別僅僅是入?yún)⒎祬€(gè)數(shù)的排列組合。?
用我們常見(jiàn)的 Stream 編程熟悉一下
?下面這段代碼如果你項(xiàng)目中有用 stream 編程那肯定很熟悉,有一個(gè) Student 的 list,你想把它轉(zhuǎn)換成一個(gè) map,key 是 student 對(duì)象的 id,value 就是 student 對(duì)象本身。List?studentList?=?gen();
Map?map?=?studentList?.stream()
????????.collect(Collectors.toMap(Student::getId,?a?->?a,?(a,?b)?->?a));
把 Lamda 表達(dá)式的部分提取出來(lái)。Collectors.toMap(Student::getId,?a?->?a,?(a,?b)?->?a)
由于我們還沒(méi)見(jiàn)過(guò) :: 這種形式,先打回原樣,這里只是讓你預(yù)熱一下。Collectors.toMap(a?->?a.getId(),?a?->?a,?(a,?b)?->?a)
為什么它被寫成這個(gè)樣子呢?我們看下 Collectors.toMap 這個(gè)方法的定義就明白了。public?static??Collector>?toMap(
????????Function?super?T,???extends?K>?keyMapper,
????????Function?super?T,???extends?U>?valueMapper,
????????BinaryOperator?mergeFunction)?
{
????return?toMap(keyMapper,?valueMapper,?mergeFunction,?HashMap::new);
}
看,入?yún)⒂腥齻€(gè),分別是Function,F(xiàn)unction,BinaryOperator,其中 BinaryOperator 只是繼承了 BiFunction 并擴(kuò)展了幾個(gè)方法,我們沒(méi)有用到,所以不妨就把它當(dāng)做BiFunction。?還記得 Function 和 BiFunction 吧??Function??R apply(T t)BiFunction?R apply(T t, U u)?那就很容易理解了。?第一個(gè)參數(shù)a -> a.getId()就是 R apply(T t) 的實(shí)現(xiàn),入?yún)⑹?Student 類型的對(duì)象 a,返回 a.getId()?第二個(gè)參數(shù)a -> a也是 R apply(T t) 的實(shí)現(xiàn),入?yún)⑹?Student 類型的 a,返回 a 本身?第三個(gè)參數(shù)(a, b) -> a是 R apply(T t, U u) 的實(shí)現(xiàn),入?yún)⑹荢tudent 類型的 a 和 b,返回是第一個(gè)入?yún)?a,Stream 里把它用作當(dāng)兩個(gè)對(duì)象 a 和 b 的 key 相同時(shí),value 就取第一個(gè)元素 a?其中第二個(gè)參數(shù) a -> a 在 Stream 里表示從 list 轉(zhuǎn)為 map 時(shí)的 value 值,就用原來(lái)的對(duì)象自己,你肯定還見(jiàn)過(guò)這樣的寫法。Collectors.toMap(a?->?a.getId(),?Function.identity(),?(a,?b)?->?a)
為什么可以這樣寫,給你看 Function 類的全貌你就明白了。@FunctionalInterface
public?interface?Function<T,?R>?{
????R?apply(T?t);?
????...
????static??Function?identity()? {
????????return?t?->?t;
????}
}
看到了吧,identity 這個(gè)方法,就是幫我們把表達(dá)式給實(shí)現(xiàn)了,就不用我們自己寫了,其實(shí)就是包了個(gè)方法。這回知道一個(gè)函數(shù)式接口,為什么有好多還要包含一堆默認(rèn)方法和靜態(tài)方法了吧?就是干這個(gè)事用的。?我們?cè)賮?lái)試一個(gè),Predicate 里面有這樣一個(gè)默認(rèn)方法。@FunctionalInterface
public?interface?Predicate<T>?{
????boolean?test(T?t);
????default?Predicate?and(Predicate?super?T>?other)? {
????????Objects.requireNonNull(other);
????????return?(t)?->?test(t)?