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