自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

原來(lái) Lambda 表達(dá)式是這樣寫的

開(kāi)發(fā) 后端
Lambda 表達(dá)式非常方便,在項(xiàng)目中一般在 stream 編程中用的比較多。

 [[434281]]

低并發(fā)的友友們好,我是閃客。

Lambda 表達(dá)式非常方便,在項(xiàng)目中一般在 stream 編程中用的比較多。 

  1. List<Student> studentList = gen();  
  2. Map<String, Student> map = studentList .stream()  
  3.         .collect(Collectors.toMap(Student::getId, a -> a, (a, b) -> a)); 

理解一個(gè) Lambda 表達(dá)式就三步:

1. 確認(rèn) Lamda 表達(dá)式的類型

2. 找到要實(shí)現(xiàn)的方法

3. 實(shí)現(xiàn)這個(gè)方法 

就這三步,沒(méi)其他的了。而每一步,都非常非常簡(jiǎn)單,以至于我分別展開(kāi)講一下,你就懂了。

確認(rèn) Lambda 表達(dá)式的類型

 能用 Lamda 表達(dá)式來(lái)表示的類型,必須是一個(gè)函數(shù)式接口,而函數(shù)式接口,就是只有一個(gè)抽象方法的接口。

 我們看下非常熟悉的 Runnable 接口在 JDK 中的樣子就明白了。 

  1. @FunctionalInterface  
  2. public interface Runnable {  
  3.     public abstract void run();  

這就是一個(gè)標(biāo)準(zhǔn)的函數(shù)式接口。

因?yàn)橹挥幸粋€(gè)抽象方法。而且這個(gè)接口上有個(gè)注解

@FunctionalInterface

這個(gè)僅僅是在編譯期幫你檢查你這個(gè)接口是否符合函數(shù)式接口的條件,比如你沒(méi)有任何抽象方法,或者有多個(gè)抽象方法,編譯是無(wú)法通過(guò)的。 

  1. // 沒(méi)有實(shí)現(xiàn)任何抽象方法的接口  
  2. @FunctionalInterface  
  3. public interface MyRunnable {}  
  4. // 編譯后控制臺(tái)顯示如下信息  
  5. Error:(3, 1) java:   
  6.   意外的 @FunctionalInterface 注釋  
  7.   MyRunnable 不是函數(shù)接口  
  8.     在 接口 MyRunnable 中找不到抽象方法 

再稍稍復(fù)雜一點(diǎn),Java 8 之后接口中是允許使用默認(rèn)方法和靜態(tài)方法的,而這些都不算抽象方法,所以也可以加在函數(shù)式接口里。

 看看你可能不太熟悉又有點(diǎn)似曾相識(shí)的一個(gè)接口。 

  1. @FunctionalInterface  
  2. public interface Consumer<T> {  
  3.     void accept(T t);  
  4.     default Consumer<T> andThen(Consumer<? super T> after) {...}  

看,只有一個(gè)抽象方法,還有一個(gè)默認(rèn)方法(方法體的代碼省略了),這個(gè)也不影響它是個(gè)函數(shù)式接口。再看一個(gè)更復(fù)雜的,多了靜態(tài)方法,這同樣也是個(gè)函數(shù)式接口,因?yàn)樗匀恢挥幸粋€(gè)抽象方法。自行體會(huì)吧。 

  1. @FunctionalInterface  
  2. public interface Predicate<T> {  
  3.     boolean test(T t);      
  4.     default Predicate<T> and(Predicate<? super T> other) {...}  
  5.     default Predicate<T> negate() {...}  
  6.     default Predicate<T> or(Predicate<? super T> other) {...}  
  7.      static <T> Predicate<T> isEqual(Object targetRef) {...}  
  8.     static <T> Predicate<T> not(Predicate<? super T> target) {...}  

先不用管這些方法都是干嘛的,這些類在 Stream 設(shè)計(jì)的方法中比比皆是,我們就先記住這么一句話,Lambda 表達(dá)式需要的類型為函數(shù)式接口,函數(shù)式接口里只有一個(gè)抽象方法,就夠了,以上三個(gè)例子都屬于函數(shù)式接口。 

恭喜你,已經(jīng)學(xué)會(huì)了 Lambda 表達(dá)式最難的部分,就是認(rèn)識(shí)函數(shù)式接口。

 找到要實(shí)現(xiàn)的方法 

Lambda 表達(dá)式就是實(shí)現(xiàn)一個(gè)方法,什么方法呢?就是剛剛那些函數(shù)式接口中的抽象方法。

 那就太簡(jiǎn)單了,因?yàn)楹瘮?shù)式接口有且只有一個(gè)抽象方法,找到它就行了。我們嘗試把剛剛那幾個(gè)函數(shù)式接口的抽象方法找到。 

  1. @FunctionalInterface  
  2. public interface Runnable {  
  3.     public abstract void run();  
  4.  
  5. @FunctionalInterface  
  6. public interface Consumer<T> {  
  7.     void accept(T t);  
  8.     default Consumer<T> andThen(Consumer<? super T> after) {...}  
  9.  
  10. @FunctionalInterface  
  11. public interface Predicate<T> {  
  12.     boolean test(T t);  
  13.     default Predicate<T> and(Predicate<? super T> other) {...}  
  14.     default Predicate<T> negate() {...} 
  15.     default Predicate<T> or(Predicate<? super T> other) {...}  
  16.     static <T> Predicate<T> isEqual(Object targetRef) {...}  
  17.     static <T> Predicate<T> not(Predicate<? super T> target) {...} 

好了,這就找到了,簡(jiǎn)單吧!

實(shí)現(xiàn)這個(gè)方法 

Lambda 表達(dá)式就是要實(shí)現(xiàn)這個(gè)抽象方法,如果不用 Lambda 表達(dá)式,你一定知道用匿名類如何去實(shí)現(xiàn)吧?比如我們實(shí)現(xiàn)剛剛 Predicate 接口的匿名類。 

  1. Predicate<String> predicate = new Predicate<String>() {  
  2.     @Override  
  3.     public boolean test(String s) {  
  4.         return s.length() != 0;  
  5.     }  
  6. }; 

那如果換成 Lambda 表達(dá)式呢?就像這樣。 

  1. Predicate<String> predicate =   
  2.     (String s) -> {  
  3.         return s.length() != 0;  
  4.     }; 

 看出來(lái)了么?這個(gè) Lambda 語(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),去掉它。 

  1. Predicate<String> predicate =  
  2.     (s) -> {  
  3.         return s.length() != 0;  
  4.     }; 

 當(dāng)只有一個(gè)參數(shù)時(shí),括號(hào)也可以去掉。 

  1. Predicate<String> predicate =   
  2.     s -> {  
  3.         return s.length() != 0;  
  4.     }; 

再看代碼塊部分,方法體中只有一行代碼,可以把花括號(hào)和 return 關(guān)鍵字都去掉。 

  1. Predicate<String> p = s -> s.length() != 0; 

這樣看是不是就熟悉點(diǎn)了?

來(lái),再讓我們實(shí)現(xiàn)一個(gè) Runnable 接口。 

  1. @FunctionalInterface  
  2. public interface Runnable {  
  3.     public abstract void run();  
  4.  
  5. Runnable r = () -> System.out.println("I am running"); 

你看,這個(gè)方法沒(méi)有入?yún)?,所以前面括?hào)里的參數(shù)就沒(méi)有了,這種情況下括號(hào)就不能省略。

 通常我們快速新建一個(gè)線程并啟動(dòng)時(shí),是不是像如下的寫法,熟悉吧?

  1. new Thread(() -> System.out.println("I am running")).start(); 

多個(gè)入?yún)?/h2>

之前我們只嘗試了一個(gè)入?yún)?,接下?lái)我們看看多個(gè)入?yún)⒌摹?nbsp;

  1. @FunctionalInterface  
  2. public interface BiConsumer<T, U> {  
  3.     void accept(T t, U u);  
  4.     // default methods removed  

然后看看一個(gè)用法,是不是一目了然。 

  1. BiConsumer<Random, Integer> randomNumberPrinter =   
  2.         (random, number) -> {  
  3.             for (int i = 0; i < number; i++) {  
  4.                 System.out.println("next random = " + random.nextInt());  
  5.             } 
  6.         };     
  7.  randomNumberPrinter.accept(new Random(314L), 5)); 

剛剛只是多個(gè)入?yún)?,那我們?cè)偌觽€(gè)返回值。 

  1. @FunctionalInterface  
  2. public interface BiFunction<T, U, R> {  
  3.     R apply(T t, U u);  
  4.     // default methods removed 
  5.   
  6. // 看個(gè)例子  
  7. BiFunction<String, String, Integer> findWordInSentence =  
  8.     (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包下的類。

 BiConsumer

BiFunction

BinaryOperator

BiPredicate

BooleanSupplier

Consumer

DoubleBinaryOperator

DoubleConsumer

DoubleFunction

DoublePredicate

DoubleSupplier

DoubleToIntFunction

DoubleToLongFunction

DoubleUnaryOperator

Function

IntBinaryOperator

IntConsumer

IntFunction

IntPredicate

IntSupplier

IntToDoubleFunction

IntToLongFunction

IntUnaryOperator

LongBinaryOperator

LongConsumer

LongFunction

LongPredicate

LongSupplier

LongToDoubleFunction

LongToIntFunction

LongUnaryOperator

ObjDoubleConsumer

ObjIntConsumer

ObjLongConsumer

Predicate

Supplier

ToDoubleBiFunction

ToDoubleFunction

ToIntBiFunction

ToIntFunction

ToLongBiFunction

ToLongFunction

UnaryOperator

 別看暈了,我們分分類就好了。可以注意到很多類前綴是 Int,Long,Double 之類的,這其實(shí)是指定了入?yún)⒌奶囟愋?,而不再是一個(gè)可以由用戶自定義的泛型,比如說(shuō) DoubleFunction。 

  1. @FunctionalInterface  
  2. public interface DoubleFunction<R> {  
  3.     R apply(double value);  

這完全可以由更自由的函數(shù)式接口 Function 來(lái)實(shí)現(xiàn)。 

  1. @FunctionalInterface  
  2. public interface Function<T, R> {  
  3.     R apply(T t);  

那我們不妨先把這些特定類型的函數(shù)式接口去掉(我還偷偷去掉了 XXXOperator 的幾個(gè)類,因?yàn)樗鼈兌际抢^承了別的函數(shù)式接口),然后再排排序,看看還剩點(diǎn)啥。

 Consumer

Function

Predicate 

BiConsumer

BiFunction

BiPredicate 

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)?,有返回值?/p>

consumer:有入?yún)ⅲ瑹o(wú)返回值。

predicate:有入?yún)ⅲ祷?boolean 值

function:有入?yún)?,有返回?nbsp;

然后帶 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ì)象本身。 

  1. List<Student> studentList = gen();  
  2. Map<String, Student> map = studentList .stream()  
  3.         .collect(Collectors.toMap(Student::getId, a -> a, (a, b) -> a)); 

把 Lamda 表達(dá)式的部分提取出來(lái)。 

  1. Collectors.toMap(Student::getId, a -> a, (a, b) -> a) 

由于我們還沒(méi)見(jiàn)過(guò) :: 這種形式,先打回原樣,這里只是讓你預(yù)熱一下。 

  1. Collectors.toMap(a -> a.getId(), a -> a, (a, b) -> a) 

為什么它被寫成這個(gè)樣子呢?我們看下 Collectors.toMap 這個(gè)方法的定義就明白了。 

  1. public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(  
  2.         Function<? super T, ? extends K> keyMapper, 
  3.         Function<? super T, ? extends U> valueMapper,  
  4.         BinaryOperator<U> mergeFunction)   
  5.  
  6.     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ò)這樣的寫法。 

  1. Collectors.toMap(a -> a.getId(), Function.identity(), (a, b) -> a) 

為什么可以這樣寫,給你看 Function 類的全貌你就明白了。 

  1. @FunctionalInterface  
  2. public interface Function<T, R> {  
  3.     R apply(T t);   
  4.     ...  
  5.     static <T> Function<T, T> identity() {  
  6.         return t -> t; 
  7.     }  

看到了吧,identity 這個(gè)方法,就是幫我們把表達(dá)式給實(shí)現(xiàn)了,就不用我們自己寫了,其實(shí)就是包了個(gè)方法。這回知道一個(gè)函數(shù)式接口,為什么有好多還要包含一堆默認(rèn)方法和靜態(tài)方法了吧?就是干這個(gè)事用的。

 我們?cè)賮?lái)試一個(gè),Predicate 里面有這樣一個(gè)默認(rèn)方法。 

  1. @FunctionalInterface  
  2. public interface Predicate<T> {  
  3.     boolean test(T t);  
  4.     default Predicate<T> and(Predicate<? super T> other) {  
  5.         Objects.requireNonNull(other);  
  6.         return (t) -> test(t) && other.test(t);  
  7.     }  

它能干嘛用呢?我來(lái)告訴你,如果沒(méi)有這個(gè)方法,有一段代碼你可能會(huì)這樣寫。 

  1. Predicate<String> p =   
  2.     s -> (s != null) &&   
  3.     !s.isEmpty() &&   
  4.     s.length() < 5

如果利用上這個(gè)方法,就可以變成如下這種優(yōu)雅形式。 

  1. Predicate<String> nonNull = s -> s != null; 
  2. Predicate<String> nonEmpty = s -> s.isEmpty();  
  3. Predicate<String> sshorterThan5 = s -> s.length() < 5 
  4. Predicate<String> p = nonNull.and(nonEmpty).and(shorterThan5); 

自行體會(huì)吧。

 方法引用 

那我們回過(guò)頭再看剛剛的 Student::getId 這種寫法。當(dāng)方法體中只有一個(gè)方法調(diào)用時(shí),就可以作這樣的簡(jiǎn)化。

 比如這個(gè) a -> a.getId() 就只是對(duì) Student 對(duì)象上 getId() 這個(gè)方法的調(diào)用,那么就可以寫成 Student::getId 這種形式。

 再看幾個(gè)例子 

  1. Function<String, Integer> toLength = s -> s.length();  
  2. Function<String, Integer> toLength = String::length;  
  3. Function<User, String> getName = user -> user.getName();  
  4. Function<String, Integer> toLength = User::getName;  
  5. Consumer<String> printer = s -> System.out.println(s);  
  6. Consumer<String> printer = System.out::println; 

如果是構(gòu)造方法的話,也可以簡(jiǎn)化。 

  1. Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();  
  2. Supplier<List<String>> newListOfStrings = ArrayList::new; 

總結(jié)

學(xué)會(huì)理解和寫 Lambda 表達(dá)式,別忘了最開(kāi)始的三步。 

1. 確認(rèn) Lamda 表達(dá)式的類型

2. 找到要實(shí)現(xiàn)的方法

3. 實(shí)現(xiàn)這個(gè)方法

Lamda 表達(dá)式的類型就是函數(shù)式接口,要實(shí)現(xiàn)的方法就是函數(shù)式接口里那個(gè)唯一的抽象方法,實(shí)現(xiàn)這個(gè)方法的方式就是參數(shù)塊 + 小箭頭 + 方法體,其中參數(shù)塊和方法體都可以一定程度上簡(jiǎn)化它的寫法。

是不是很簡(jiǎn)單了!

以上代碼例子,都來(lái)源于官方的教程,英語(yǔ)好的同學(xué)可以看看,是最科學(xué)的 Lamda 表達(dá)式教程了。

https://dev.java/learn/tutorial/getting-to-know-the-language/lambda-expressions/lambdas.html

的今天的文章主要就是講怎么寫出 Lambda 表達(dá)式,至于原理,之后再說(shuō)。這里提個(gè)引子,你覺(jué)得 Lambda 表達(dá)式是匿名類的簡(jiǎn)化么?按照官方的說(shuō)法,Lamda 表達(dá)式在某些情況下就是匿名類的一種更簡(jiǎn)單的寫法,但是從字節(jié)碼層面看,完全不同,這又是怎么回事呢?

帶著這個(gè)問(wèn)題,給自己埋個(gè)坑,我們下講說(shuō)說(shuō) Lambda 表達(dá)式背后的實(shí)現(xiàn)原理。 

【責(zé)任編輯:龐桂玉 TEL:(010)68476606】

 

責(zé)任編輯:龐桂玉 來(lái)源: 程序員書庫(kù)
相關(guān)推薦

2012-06-26 10:03:58

JavaJava 8lambda

2009-09-11 09:48:27

Linq Lambda

2009-09-09 13:01:33

LINQ Lambda

2009-09-15 15:18:00

Linq Lambda

2022-12-05 09:31:51

接口lambda表達(dá)式

2024-03-25 13:46:12

C#Lambda編程

2009-08-27 09:44:59

C# Lambda表達(dá)

2009-09-17 10:40:22

Linq Lambda

2009-09-15 17:30:00

Linq Lambda

2009-09-17 09:44:54

Linq Lambda

2020-10-16 06:40:25

C++匿名函數(shù)

2009-07-09 09:51:07

Lambda表達(dá)式C#

2009-08-26 16:17:23

C# Lambda表達(dá)

2009-08-31 17:11:37

Lambda表達(dá)式

2009-08-27 09:57:50

C# Lambda表達(dá)

2009-09-17 09:09:50

Lambda表達(dá)式Linq查詢

2009-08-10 09:41:07

.NET Lambda

2013-04-10 10:58:19

LambdaC#

2013-04-07 15:44:26

Java8Lambda

2009-09-09 17:14:17

Linq lambda
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)