Lambda表達式,相信大家都耳有所聞,而且不少小伙伴在日常的工作中也在使用。但說到函數式接口,可能有一些即使會使用Lambda表達式的小伙伴也會覺得陌生。今天,指北君就將帶領大家對Lambda、及其所使用的一些和函數式接口相關的知識點進行一個全面的學習。函數式接口所涉及的知識點包含:java.util.function包,@FunctoinInterface注解,Lambda表達式,雙冒號操作符。同時,我們還將對函數式接口的實現原理進行深入的剖析。
概述
函數式接口將分為三個篇章來為大家介紹:
- (應用篇一)(1)函數式接口的來源,(2)Lambda表達式,(3)雙冒號運算符
- (應用篇二)(4)詳細介紹@FunctionInterface注解(5)對java.util.function包進行解讀
- (原理篇)介紹函數式接口的實現原理 應用篇將階段相關的JDK源碼以及給出典型的示例代碼 原理篇則從編譯、JVM維度來分析函數式接口的實現原理,具有一定深度,需要讀者具備一定的底層知識。
說明:源碼使用的版本為JDK-11.0.11
什么是函數式接口
【閱讀導引】:本節為概念性知識,純技術向伙伴可跳過
在分析具體內容之前,指北君帶領大家來對函數式接口做個基本的認知。函數式接口是JAVA語言為引入函數式編程而增加的特性,也即是說函數式接口式Java實現函數式編程的具體方式。那么,函數式編程到底是什么?他和面向對象編程又有什么關系?它能為我們帶來什么?我們又是否真的需要函數式編程?有很多小伙伴,可能和指北君一樣,是以面向對象語言開啟的編程世界的,對于函數式編程其實很陌生。所以,指北君在這里先給大家引薦編程界的三大流派(當然還有別的流派):過程式,函數式,對象式:
編程范式
函數式編程的思想脫胎于數學理論,也就是我們通常所說的λ演算(λ-calculus)。這也是為什么Java8中引入的函數式編程叫Lambda表達式的原因吧。如同數學中的函數一樣,函數式編程范式中的函數有獨特的特性,也就是通常說的無狀態或引用透明性。一個函數的輸出由且僅由其輸入決定,同樣的輸入永遠會產生同樣的輸出。
函數式編程的定義:"函數式編程是一種編程范式。它把計算當成是數學函數的求值,從而避免改變狀態和使用可變數據。它是一種聲明式的編程范式,通過表達式和聲明而不是語句來編程。" 函數式編程的代碼通常更加簡潔,但是不一定易懂。
近年來,隨著多核平臺和并發計算的發展,函數式編程的無狀態特性,在處理這些問題時有著其他編程范式不可比擬的天然優勢。這種發展也就進一步促使了Java引入函數式編程這一特性。
一個簡單示例
指北君先給大家展示一個簡單的函數式編程的示例:
/**
* 簡單的函數式編程示例
*/
public static void lambdaDemo1() {
// 準備測試數據
Integer[] data = new Integer[] {1, 2, 3};
List< Integer > list = Arrays.asList(data);
// 簡單示例:轉換單位并打印數據
list.forEach(x - > System.out.println(String.format("Cents into Yuan: %.2f", x/100.0)));
}
不熟悉Lambda表達式的小伙伴可能會好奇其中的語句:x -> System.out.println(String.format("Cents into Yuan: %.2f", x/100.0))
,這是什么呢?這就是我們的Lambda表達式。通常,我們要訪問List對象,需要通過for、while等控制循環語句,并在循環中完成相關工作。有了函數式編程后,我們就可以使用Lambda表達式來完成對應的功能,是不是很簡潔!小伙伴們可能會奇怪,難道Lambda自動做了循環?當然不是,這里的循環控制并沒有減少,只是在forEach方法中而已。我們打開默認的迭代器forEach實現方法(ArrayList的forEach實現有差異,總體邏輯一致),代碼顯示forEach循環,并在循環中執行參數的函數邏輯。
default void forEach(Consumer< ? super T > action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
既然沒有省略控制邏輯,難道我們費這么大的力氣引入這個東東就只是為了簡潔點?指北君畫了一下調用邏輯,參見下圖
從上圖中大家是不是隱約可以看出:這種方式可以將控制部分和業務處理部分進行解耦,業務處理代碼更容易集中。
我們在分析forEach源碼的時候,看到forEach的參數類型為Consumer,打開Consumer源碼(主要接口聲明部分):
@FunctionalInterface
public interface Consumer< T > {
...
}
小伙伴們是不是發現這就是一個簡單的接口,接口使用了@FunctionInterface的接口,這是不是對Lambda表達式使用位置的約束呢?這個問題我們將在接下來的幾個章節給出答案。
Lambda表達式
在示例部分,指北君展示了在Java中如何Lambda進行函數式編程,小伙伴們是不是躍躍欲試想要動手了呢?在動手之前指北君先帶領大家了全面學習Lambda表達式的語法。下面給出幾種常見的Lambda代碼片段(代碼僅截取部分,無上下文):
() - > System.out.println("demo")
...
list.forEach(x - > System.out.print(x));
...
map.forEach((x, y) - > {
System.out.print(x);
System.out.println(y);
});
...
(Integer x, String y) - > System.out.println("x: " + x ", y: " + y);
從上面代碼片段,可以看出Lambda表達式是通過->操作符來連接的,左邊為參數部分,右邊為表達式主體。
Lambda表達式語法:
參數說明:([[type] parameter [, ...]])
- 參數包括在圓括號內,參數數量可以0到多個,多個參數通過逗號“,”分割,例如(x, y)->
- 參數類型可明確聲明,也可以省略,省略時根據上下文進行推斷, 例如:(x)->, (int x)->
- 無參數,直接使用括號,例如:()->
- 一個參數時,且參數類型省略,則括號可以省略 x->
表達式主體:
- 由0到多條語句組成
- 只有一條語句時,語句塊符號“{}”可省略,此時語句的結果將作為返回值,例如:->x*x, ->System.out.print(x)。
- 超過一條語句時,必須使用語句塊符號“{}”包含起來。
- 帶return關鍵字必須用代碼塊,例如:->{return x+x}。
常見的組合形式:
(int a, int b) - > { return a + b; }
() - > System.out.println("Demo")
(String s) - > {
System.out.println(s);
}
() - > 42
() - > { return 3.1415 };
啟動線程
new Thread(
() - > System.out.println("start in thread.")
).start();
其他的代碼遵循基本的Java語法,小伙伴們現在就可以大展拳腳,試試通過Lambda表達式進行函數式編程。
雙冒號操作符
經過上一節的實踐,小伙伴們是不是很興奮了,可能有些小伙伴會問,Java類中的的方法也是函數,我可不可以在傳入Lambda表達式的地方傳入普通方法呢?類似下面這種效果:
List< String > list = new ArrayList< String >();
...
list.forEach(xxxMethod());
想法是沒有問題的,但是形式錯誤了,首先xxxMethod()會直接觸發方法執行,并且返回的類型也不匹配forEach方法。那么,正確的形式應該如何寫呢?這就需要我們的雙冒號云算法登場了。雙冒號云算符標準名稱為eta-conversion,有下面四種常用場景
- 實例方法引用 object::instanceMethod
- 靜態方法引用 Class::staticMethod
- 實例方法引用(實例作為參數傳入) Class::instanceMethod
- 構造方法引用 Class:new
- 無參數:Supplier
- 一個參數:Function
- 二個參數:BiFunction
- 更多:自定義函數接口
示例代碼
public class FunctionInterfaceInvoke {
public static void main(String[] args) {
// 1-1 構造方法(無參數),編譯會做參數檢查(包含輸入參數和返回值)
Supplier< FunctionInterfaceInvoke > s = FunctionInterfaceInvoke::new;
s.get();
//1-2 構造方法(1個參數)
IntFunction< FunctionInterfaceInvoke > func = FunctionInterfaceInvoke::new;
func.apply(1);
// 1-3 構造方法(多個參數)
BiFunction< Integer, Integer, FunctionInterfaceInvoke > func2 = FunctionInterfaceInvoke::new;
func2.apply(1, 2);
// 2 靜態方法
Consumer< Integer > sta1 = FunctionInterfaceInvoke::staticMethod;
sta1.accept(1);
// 3 實例方法
IntConsumer sta2 = new FunctionInterfaceInvoke()::instanceMethod;
sta2.accept(2);
}
public FunctionInterfaceInvoke() {
System.out.println("none parameters");
}
public FunctionInterfaceInvoke(int p1) {
System.out.println("constructor whith one parameter: " + p1);
}
public FunctionInterfaceInvoke(Integer p1, Integer p2) {
System.out.println(String.format("constructor whith 2 parameters %1s, %2s", p1, p2));
}
public static void staticMethod(Integer p1) {
System.out.println("static method:" + p1);
}
public void instanceMethod(int p1) {
System.out.println("instance method:"+p1);
}
}
小結
函數式接口應用篇的第一部分就給大家介紹到這里,本篇我們介紹了什么是函數式編程,一個簡單示例,Lambda表達式詳細說明和雙冒號操作的使用。下一篇我們將會繼續介紹函數式接口的應用,學習 @FunctionInterface注解和java.util.function包中的接口。
-
接口
+關注
關注
33文章
8885瀏覽量
152960 -
源碼
+關注
關注
8文章
665瀏覽量
30050 -
函數
+關注
關注
3文章
4365瀏覽量
63840 -
Lambda
+關注
關注
0文章
30瀏覽量
10056
發布評論請先 登錄
相關推薦






評論