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