还看不懂同事的代码?Lambda 表达式、函数接口了解一下
本文經授權轉載自微信公眾號:未讀代碼
Java 8?早已經在2014 年 3月 18日發布,毫無疑問?Java 8?對 Java 來說絕對算得上是一次重大版本更新,它包含了十多項語言、庫、工具、JVM 等方面的新特性。
比如提供了語言級的匿名函數,也就是被官方稱為?Lambda?的表達式語法(外界也稱為閉包,Lambda?的引入也讓流式操作成為可能,減少了代碼編寫的復雜性),比如函數式接口,方法引用,重復注解。再比如?Optional?預防空指針,Stearm?流式操作,LocalDateTime?時間操作等。
這一次主要介紹一下 Lambda 的相關情況。
Lambda介紹
Lambda?名字來源于希臘字母表中排序第十一位的字母 λ,大寫為Λ,英語名稱為?Lambda。在 Java 中?Lambda?表達式(lambda expression)是一個匿名函數,在編寫 Java 中的?Lambda?的時候,你也會發現?Lambda?不僅沒有函數名稱,有時候甚至連入參和返回都可以省略,這也讓代碼變得更加緊湊。
上面說了這次是介紹?Lambda?表達式,為什么要介紹函數接口呢?其實 Java 中的函數接口在使用時,可以隱式的轉換成?Lambda?表達式,在?Java 8中已經有很多接口已經聲明為函數接口,如 Runnable、Callable、Comparator 等。
函數接口介紹
函數接口的例子可以看下?Java 8?中的?Runnable?源碼(去掉了注釋)。
@FunctionalInterfacepublic interface Runnable { public abstract void run();}
那么什么樣子的接口才是函數接口呢?有一個很簡單的定義,也就是只有一個抽象函數的接口,函數接口使用注解?@FunctionalInterface?進行聲明(注解聲明不是必須的,如果沒有注解,也是只有一個抽象函數,依舊會被認為是函數接口)。多一個或者少一個抽象函數都不能定義為函數接口,如果使用了函數接口注解又不止一個抽象函數,那么編譯器會拒絕編譯。函數接口在使用時候可以隱式的轉換成 Lambda 表達式。
Java 8?中很多有很多不同功能的函數接口定義,都放在了?Java 8?新增的?java.util.function包內。下面是一些關于?Java 8?中函數接口功能的描述。
| BiConsumer | 代表了一個接受兩個輸入參數的操作,并且不返回任何結果 |
| BiFunction | 代表了一個接受兩個輸入參數的方法,并且返回一個結果 |
| BinaryOperator | 代表了一個作用于于兩個同類型操作符的操作,并且返回了操作符同類型的結果 |
| BiPredicate | 代表了一個兩個參數的boolean值方法 |
| BooleanSupplier | 代表了boolean值結果的提供方 |
| Consumer | 代表了接受一個輸入參數并且無返回的操作 |
| DoubleBinaryOperator | 代表了作用于兩個double值操作符的操作,并且返回了一個double值的結果。 |
| DoubleConsumer | 代表一個接受double值參數的操作,并且不返回結果。 |
| DoubleFunction | 代表接受一個double值參數的方法,并且返回結果 |
| DoublePredicate | 代表一個擁有double值參數的boolean值方法 |
| DoubleSupplier | 代表一個double值結構的提供方 |
| DoubleToIntFunction | 接受一個double類型輸入,返回一個int類型結果。 |
| DoubleToLongFunction | 接受一個double類型輸入,返回一個long類型結果 |
| DoubleUnaryOperator | 接受一個參數同為類型double,返回值類型也為double 。 |
| Function | 接受一個輸入參數,返回一個結果。 |
| IntBinaryOperator | 接受兩個參數同為類型int,返回值類型也為int 。 |
| IntConsumer | 接受一個int類型的輸入參數,無返回值 。 |
| IntFunction | 接受一個int類型輸入參數,返回一個結果 。 |
| IntPredicate | 接受一個int輸入參數,返回一個布爾值的結果。 |
| IntSupplier | 無參數,返回一個int類型結果。 |
| IntToDoubleFunction | 接受一個int類型輸入,返回一個double類型結果 。 |
| IntToLongFunction | 接受一個int類型輸入,返回一個long類型結果。 |
| IntUnaryOperator | 接受一個參數同為類型int,返回值類型也為int 。 |
| LongBinaryOperator | 接受兩個參數同為類型long,返回值類型也為long。 |
| LongConsumer | 接受一個long類型的輸入參數,無返回值。 |
| LongFunction | 接受一個long類型輸入參數,返回一個結果。 |
| LongPredicate | 接受一個long輸入參數,返回一個布爾值類型結果。 |
| LongSupplier | 無參數,返回一個結果long類型的值。 |
| LongToDoubleFunction | 接受一個long類型輸入,返回一個double類型結果。 |
| LongToIntFunction | 接受一個long類型輸入,返回一個int類型結果。 |
| LongUnaryOperator | 接受一個參數同為類型long,返回值類型也為long。 |
| ObjDoubleConsumer | 接受一個object類型和一個double類型的輸入參數,無返回值。 |
| ObjIntConsumer | 接受一個object類型和一個int類型的輸入參數,無返回值。 |
| ObjLongConsumer | 接受一個object類型和一個long類型的輸入參數,無返回值。 |
| Predicate | 接受一個輸入參數,返回一個布爾值結果。 |
| Supplier | 無參數,返回一個結果。 |
| ToDoubleBiFunction | 接受兩個輸入參數,返回一個double類型結果 |
| ToDoubleFunction | 接受一個輸入參數,返回一個double類型結果 |
| ToIntBiFunction | 接受兩個輸入參數,返回一個int類型結果。 |
| ToIntFunction | 接受一個輸入參數,返回一個int類型結果。 |
| ToLongBiFunction | 接受兩個輸入參數,返回一個long類型結果。 |
| ToLongFunction | 接受一個輸入參數,返回一個long類型結果。 |
| UnaryOperator | 接受一個參數為類型T,返回值類型也為T。 |
(上面表格來源于菜鳥教程)
Lambda語法
Lambda 的語法主要是下面幾種。
1.?(params) -> expression
2. (params) -> {statements;}
Lambda 的語法特性。
使用?->?分割 Lambda 參數和處理語句。
類型可選,可以不指定參數類型,編譯器可以自動判斷。
圓括號可選,如果只有一個參數,可以不需要圓括號,多個參數必須要圓括號。
花括號可選,一個語句可以不用花括號,多個參數則花括號必須。
返回值可選,如果只有一個表達式,可以自動返回,不需要 return 語句;花括號中需要 return 語法。
6. Lambda 中引用的外部變量必須為 final 類型,內部聲明的變量不可修改,內部聲明的變量名稱不能與外部變量名相同。
舉幾個具體的例子, params 在只有一個參數或者沒有參數的時候,可以直接省略不寫,像這樣。
// 1.不需要參數,沒有返回值,輸出 hello()->System.out.pritnln("hello");// 2.不需要參數,返回 hello()->"hello";
// 3. 接受2個參數(數字),返回兩數之和 (x, y) -> x y // 4. 接受2個數字參數,返回兩數之和 (int x, int y) -> x y // 5. 兩個數字參數,如果都大于10,返回和,如果都小于10,返回差(int x,int y) ->{ if( x > 10 && y > 10){ return x y; } if( x < 10 && y < 10){ return Math.abs(x-y); }};
通過上面的幾種情況,已經可以大致了解 Lambda 的語法結構了。
Lambda的使用
1、對于函數接口
從上面的介紹中已經知道了 Runnable 接口已經是函數接口了,它可以隱式的轉換為 Lambda 表達式進行使用,通過下面的創建線程并運行的例子看下?Java 8?中 Lambda 表達式的具體使用方式。
可以發現?Java 8?中的?Lambda?碰到了函數接口 Runnable,自動推斷了要運行的 run 方法,不僅省去了 run 方法的編寫,也代碼變得更加緊湊。
運行得到結果如下。
上面的 Runnable 函數接口里的 run 方法是沒有參數的情況,如果是有參數的,那么怎么使用呢?我們編寫一個函數接口,寫一個?say?方法接受兩個參數。
/** * 定義函數接口 */@FunctionalInterfacepublic interface FunctionInterfaceDemo { void say(String name, int age);}編寫一個測試類。
輸出結果。
我叫金庸,我今年99歲了。對于方法引用
方法引用這個概念前面還沒有介紹過,方法引用可以讓我們直接訪問類的實例或者方法,在 Lambda 只是執行一個方法的時候,就可以不用?Lambda?的編寫方式,而用方法引用的方式:實例/類::方法。這樣不僅代碼更加的緊湊,而且可以增加代碼的可讀性。
通過一個例子查看方法引用。
得到輸出結果。
Jdk8Lambda.User(name=B, age=18)Jdk8Lambda.User(name=D, age=19)Jdk8Lambda.User(name=C, age=23)Jdk8Lambda.User(name=A,?age=26)2、對于遍歷方式
Lambda 帶來了新的遍歷方式,Java 8?為集合增加了?foreach?方法,它可以接受函數接口進行操作。下面看一下?Lambda?的集合遍歷方式。
運行得到輸出。
java,golang,c ,c,python,java,golang,c ,c,python,javagolangc cpython3、對于流式操作
得益于?Lambda?的引入,讓?Java 8?中的流式操作成為可能,Java 8?提供了 stream 類用于獲取數據流,它專注對數據集合進行各種高效便利操作,提高了編程效率,且同時支持串行和并行的兩種模式匯聚計算。能充分的利用多核優勢。
流式操作如此強大,?Lambda?在流式操作中怎么使用呢?下面來感受流操作帶來的方便與高效。
流式操作一切從這里開始。
流式操作的去重?distinct和過濾?filter。
運行得到結果。
流式操作的數據轉換(也稱映射)map。
System.out.println();
// 數據收集 Set<Integer> numSet = numList.stream().map(num -> num * num).collect(Collectors.toSet()); numSet.forEach(num -> System.out.print(num ","));?}
運行得到結果。
流式操作的數學計算。
/** * 數學計算測試 */@Testpublic void mapMathTest() { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics(); System.out.println("最小值:" stats.getMin()); System.out.println("最大值:" stats.getMax()); System.out.println("個數:" stats.getCount()); System.out.println("和:" stats.getSum()); System.out.println("平均數:" stats.getAverage()); // 求和的另一種方式 Integer integer = list.stream().reduce((sum, cost) -> sum cost).get(); System.out.println(integer);}運行得到結果。
得到輸出最小值:1最大值:5個數:5和:15平均數:3.015Lambda總結
Lamdba?結合函數接口,方法引用,類型推導以及流式操作,可以讓代碼變得更加簡潔緊湊,也可以借此開發出更加強大且支持并行計算的程序,函數編程也為 Java 帶來了新的程序設計方式。但是缺點也很明顯,在實際的使用過程中可能會發現調式困難,測試表示?Lamdba?的遍歷性能并不如 for 的性能高,同事可能沒有學習導致看不懂?Lamdba?等(可以推薦來看這篇文章)。
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的还看不懂同事的代码?Lambda 表达式、函数接口了解一下的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NYOJ 108 士兵杀敌(一)
- 下一篇: NYOJ 116 士兵杀敌(二)