成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

原來 Lambda 表達式是這樣寫的

開發 后端
Lambda 表達式非常方便,在項目中一般在 stream 編程中用的比較多。

 [[434281]]

低并發的友友們好,我是閃客。

Lambda 表達式非常方便,在項目中一般在 stream 編程中用的比較多。 

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

理解一個 Lambda 表達式就三步:

1. 確認 Lamda 表達式的類型

2. 找到要實現的方法

3. 實現這個方法 

就這三步,沒其他的了。而每一步,都非常非常簡單,以至于我分別展開講一下,你就懂了。

確認 Lambda 表達式的類型

 能用 Lamda 表達式來表示的類型,必須是一個函數式接口,而函數式接口,就是只有一個抽象方法的接口。

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

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

這就是一個標準的函數式接口。

因為只有一個抽象方法。而且這個接口上有個注解

@FunctionalInterface

這個僅僅是在編譯期幫你檢查你這個接口是否符合函數式接口的條件,比如你沒有任何抽象方法,或者有多個抽象方法,編譯是無法通過的。 

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

再稍稍復雜一點,Java 8 之后接口中是允許使用默認方法和靜態方法的,而這些都不算抽象方法,所以也可以加在函數式接口里。

 看看你可能不太熟悉又有點似曾相識的一個接口。 

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

看,只有一個抽象方法,還有一個默認方法(方法體的代碼省略了),這個也不影響它是個函數式接口。再看一個更復雜的,多了靜態方法,這同樣也是個函數式接口,因為它仍然只有一個抽象方法。自行體會吧。 

  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 設計的方法中比比皆是,我們就先記住這么一句話,Lambda 表達式需要的類型為函數式接口,函數式接口里只有一個抽象方法,就夠了,以上三個例子都屬于函數式接口。 

恭喜你,已經學會了 Lambda 表達式最難的部分,就是認識函數式接口。

 找到要實現的方法 

Lambda 表達式就是實現一個方法,什么方法呢?就是剛剛那些函數式接口中的抽象方法。

 那就太簡單了,因為函數式接口有且只有一個抽象方法,找到它就行了。我們嘗試把剛剛那幾個函數式接口的抽象方法找到。 

  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) {...} 

好了,這就找到了,簡單吧!

實現這個方法 

Lambda 表達式就是要實現這個抽象方法,如果不用 Lambda 表達式,你一定知道用匿名類如何去實現吧?比如我們實現剛剛 Predicate 接口的匿名類。 

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

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

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

 看出來了么?這個 Lambda 語法由三部分組成:

參數塊:就是前面的 (String s),就是簡單地把要實現的抽象方法的參數原封不動寫在這。

小箭頭:就是 -> 這個符號。

代碼塊:就是要實現的方法原封不動寫在這。 

不過這樣的寫法你一定不熟悉,連 idea 都不會幫我們簡化成這個奇奇怪怪的樣子,別急,我要變形了!其實是對其進行格式上的簡化。

 首先看參數快部分,(String s) 里面的類型信息是多余的,因為完全可以由編譯器推導,去掉它。 

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

 當只有一個參數時,括號也可以去掉。 

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

再看代碼塊部分,方法體中只有一行代碼,可以把花括號和 return 關鍵字都去掉。 

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

這樣看是不是就熟悉點了?

來,再讓我們實現一個 Runnable 接口。 

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

你看,這個方法沒有入參,所以前面括號里的參數就沒有了,這種情況下括號就不能省略。

 通常我們快速新建一個線程并啟動時,是不是像如下的寫法,熟悉吧?

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

多個入參

之前我們只嘗試了一個入參,接下來我們看看多個入參的。 

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

然后看看一個用法,是不是一目了然。 

  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)); 

剛剛只是多個入參,那我們再加個返回值。 

  1. @FunctionalInterface  
  2. public interface BiFunction<T, U, R> {  
  3.     R apply(T t, U u);  
  4.     // default methods removed 
  5.   
  6. // 看個例子  
  7. BiFunction<String, String, Integer> findWordInSentence =  
  8.     (word, sentence) -> sentence.indexOf(word); 

發現規律了沒

 OK,看了這么多例子,不知道你發現規律了沒?

 其實函數式接口里那個抽象方法,無非就是入參的個數,以及返回值的類型。入參的個數可以是一個或者兩個,返回值可以是 void,或者 boolean,或者一個類型。那這些種情況的排列組合,就是 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 之類的,這其實是指定了入參的特定類型,而不再是一個可以由用戶自定義的泛型,比如說 DoubleFunction。 

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

這完全可以由更自由的函數式接口 Function 來實現。 

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

那我們不妨先把這些特定類型的函數式接口去掉(我還偷偷去掉了 XXXOperator 的幾個類,因為它們都是繼承了別的函數式接口),然后再排排序,看看還剩點啥。

 Consumer

Function

Predicate 

BiConsumer

BiFunction

BiPredicate 

Supplier 

哇塞,幾乎全沒了,接下來就重點看看這些。這里我就只把類和對應的抽象方法列舉出來

 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() 

看出規律了沒?上面那幾個簡單分類就是: 

supplier:沒有入參,有返回值。

consumer:有入參,無返回值。

predicate:有入參,返回 boolean 值

function:有入參,有返回值 

然后帶 Bi 前綴的,就是有兩個入參,不帶的就只有一個如參。OK,這些已經被我們分的一清二楚了,其實就是給我們提供了一個函數的模板,區別僅僅是入參返參個數的排列組合。

 用我們常見的 Stream 編程熟悉一下 

下面這段代碼如果你項目中有用 stream 編程那肯定很熟悉,有一個 Student 的 list,你想把它轉換成一個 map,key 是 student 對象的 id,value 就是 student 對象本身。 

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

把 Lamda 表達式的部分提取出來。 

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

由于我們還沒見過 :: 這種形式,先打回原樣,這里只是讓你預熱一下。 

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

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

  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);  

看,入參有三個,分別是Function,Function,BinaryOperator,其中 BinaryOperator 只是繼承了 BiFunction 并擴展了幾個方法,我們沒有用到,所以不妨就把它當做BiFunction。

 還記得 Function 和 BiFunction 吧? 

Function  R apply(T t)

BiFunction R apply(T t, U u) 

那就很容易理解了。 

第一個參數a -> a.getId()就是 R apply(T t) 的實現,入參是 Student 類型的對象 a,返回 a.getId()

 第二個參數a -> a也是 R apply(T t) 的實現,入參是 Student 類型的 a,返回 a 本身

 第三個參數(a, b) -> a是 R apply(T t, U u) 的實現,入參是Student 類型的 a 和 b,返回是第一個入參 a,Stream 里把它用作當兩個對象 a 和 b 的 key 相同時,value 就取第一個元素 a

 其中第二個參數 a -> a 在 Stream 里表示從 list 轉為 map 時的 value 值,就用原來的對象自己,你肯定還見過這樣的寫法。 

  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 這個方法,就是幫我們把表達式給實現了,就不用我們自己寫了,其實就是包了個方法。這回知道一個函數式接口,為什么有好多還要包含一堆默認方法和靜態方法了吧?就是干這個事用的。

 我們再來試一個,Predicate 里面有這樣一個默認方法。 

  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.     }  

它能干嘛用呢?我來告訴你,如果沒有這個方法,有一段代碼你可能會這樣寫。 

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

如果利用上這個方法,就可以變成如下這種優雅形式。 

  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); 

自行體會吧。

 方法引用 

那我們回過頭再看剛剛的 Student::getId 這種寫法。當方法體中只有一個方法調用時,就可以作這樣的簡化。

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

 再看幾個例子 

  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; 

如果是構造方法的話,也可以簡化。 

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

總結

學會理解和寫 Lambda 表達式,別忘了最開始的三步。 

1. 確認 Lamda 表達式的類型

2. 找到要實現的方法

3. 實現這個方法

Lamda 表達式的類型就是函數式接口,要實現的方法就是函數式接口里那個唯一的抽象方法,實現這個方法的方式就是參數塊 + 小箭頭 + 方法體,其中參數塊和方法體都可以一定程度上簡化它的寫法。

是不是很簡單了!

以上代碼例子,都來源于官方的教程,英語好的同學可以看看,是最科學的 Lamda 表達式教程了。

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

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

帶著這個問題,給自己埋個坑,我們下講說說 Lambda 表達式背后的實現原理。 

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

 

責任編輯:龐桂玉 來源: 程序員書庫
相關推薦

2009-09-09 13:01:33

LINQ Lambda

2009-09-15 15:18:00

Linq Lambda

2022-12-05 09:31:51

接口lambda表達式

2012-06-26 10:03:58

JavaJava 8lambda

2009-09-11 09:48:27

Linq Lambda

2024-03-25 13:46:12

C#Lambda編程

2009-08-27 09:44:59

C# Lambda表達

2009-09-15 17:30:00

Linq Lambda

2009-09-17 09:44:54

Linq Lambda

2009-09-17 10:40:22

Linq Lambda

2009-07-09 09:51:07

Lambda表達式C#

2013-04-07 15:44:26

Java8Lambda

2009-08-10 09:41:07

.NET Lambda

2009-09-09 17:14:17

Linq lambda

2013-04-10 10:58:19

LambdaC#

2020-10-16 06:40:25

C++匿名函數

2009-08-27 09:57:50

C# Lambda表達

2009-08-31 17:11:37

Lambda表達式

2009-09-17 09:09:50

Lambda表達式Linq查詢

2009-08-26 16:17:23

C# Lambda表達
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久综合九九 | 国产女人叫床高潮大片免费 | 欧美精品一区久久 | 精品成人一区 | 日韩欧美三级电影 | 久久久久久国产 | 欧美视频成人 | 成人国产精品久久久 | 亚洲美女一区 | 国产欧美日韩精品一区二区三区 | 黄色91在线| 亚洲高清一区二区三区 | www.中文字幕.com | 日本不卡高清视频 | 欧美久操网 | 视频在线一区 | 国产中文在线观看 | 国产特级毛片aaaaaa | 福利av在线 | 国产在线视频一区 | 日本不卡一区二区三区 | 精品国产99| 日本午夜网 | 亚洲三区视频 | 欧美男人亚洲天堂 | 欧美精品一区二区三区蜜桃视频 | 国产欧美一区二区三区另类精品 | 一区二区三区免费 | 人妖一区 | 最新日韩av| 国产精品久久久久久久久婷婷 | 久草热8精品视频在线观看 午夜伦4480yy私人影院 | 黄色一级大片在线观看 | 国产精品九九九 | 美女国内精品自产拍在线播放 | 国产成人在线视频 | 蜜桃精品噜噜噜成人av | 欧美精品久久久久久久久久 | 91资源在线 | www.一区二区三区.com | 欧美性极品xxxx做受 |