原标題:Java8的四大函數接口
前言
Java8中函數接口有很多,大概有幾十個吧,具體究竟是多少我也數不清,是以一開始看的時候感覺一臉懵逼,不過其實根本沒那麼複雜,畢竟不應該也沒必要把一個東西設計的很複雜。
幾個單詞
在學習了解之前,希望大家能記住幾個單詞,掌握這幾個單詞,什麼3,40個官方的函數接口都是小問題了,不信的話接着往下看啦。ok,那這幾個單詞呢分别是supplier提供者,consumer消費者,function函數,operation運算符,binary二進制(就是數學裡二進制一次方程那個二進制,代表2個的意思),雙重的
四大基礎函數接口
函數接口,你可以了解為對一段行為的抽象,簡單點說可以在方法就是将一段行為作為參數進行傳遞,這個行為呢,可以是一段代碼,也可以是一個方法,那你可以想象在java8之前要将一段方法作為參數傳遞隻能通過匿名内部類來實作,而且代碼很難看,也很長,函數接口就是對匿名内部類的優化。
雖然類庫中的基本函數接口特别多,但其實總體可以分成四類,就好像阿拉伯數字是無限多的,但總共就10個基本數字一樣,了解了這4個,其他的就都明白了。
Functio接口
function,顧名思義,函數的意思,這裡的函數是指數學上的函數哦,你也可以說是嚴格函數語言中的函數,例如haskell裡的,他接受一個參數,傳回一個值,永遠都是這樣,是一個恒定的,狀态不可改變的方法。其實想講函數這個徹底将明白可以再開一篇部落格了,是以這裡不詳細的說了。
上面說到,函數接口是對行為的抽象,是以我友善大家了解,就用java中的方法作例子。
Fcuntion接口是對接受一個T類型參數,傳回R類型的結果的方法的抽象,通過調用apply方法執行内容。
publicclassOperation{
publicstaticfinalintaddOne(inta){
returna+1;
}
publicstaticintoper(inta, Function action){
returnaction.apply(a);
}
pulic staticvoidmain(String[] args){
intx = 1;
inty = oper(x,x -> addOne(x));//這裡可以換成方法引用的寫法 int y = oper(x,Operation::addOne)
System.out.printf("x= %d, y = %d", x, y); // 列印結果 x=1, y=2
y = oper(x, x -> x + 3); // y = 4
y = oper(x, x -> x * 3); // y = 3
}
}

這裡的箭頭指向的位置就是形參,可以看到第二個箭頭的Lambda表達式指向了Funtion接口
Consumer 接口
Consumer 接口翻譯過來就是消費者,顧名思義,該接口對應的方法類型為接收一個參數,沒有傳回值,可以通俗的了解成将這個參數'消費掉了',一般來說使用Consumer接口往往伴随着一些期望狀态的改變或者事件的發生,例如最典型的forEach就是使用的Consumer接口,雖然沒有任何的傳回值,但是卻向控制台輸出了語句。
Consumer 使用accept對參數執行行為
publicstaticvoidmain(String[] args){
Consumer printString = s -> System.out.println(s);
printString.accept("helloWorld!");
//控制台輸出 helloWorld!
}
Supplier 接口
Supplier 接口翻譯過來就是提供者,和上面的消費者相反,該接口對應的方法類型為不接受參數,但是提供一個傳回值,通俗的了解為這種接口是無私的奉獻者,不僅不要參數,還傳回一個值,使用get()方法獲得這個傳回值
Supplier getInstance = ()->"HelloWorld!";
System.out.println(getInstance.get());
//控偶值台輸出 HelloWorld
Predicate 接口
predicate 謂語接口,顧名思義,中文中的‘是’與‘不是’是中文文法的謂語,同樣的該接口對應的方法為接收一個參數,傳回一個Boolean類型值,多用于判斷與過濾,當然你可以把他了解成特殊的Funcation,但是為了便于區分語義,還是單獨的劃了一個接口,使用test()方法執行這段行為
publicstaticvoidmain(String[] args){
Predicate predOdd = integer -> integer % 2== 1;
System.out.println(predOdd.test(5));
//控制台輸出 5
}
其他的接口
介紹完正面這四種最基本的接口,剩餘的接口就可以很容易的了解了,java8中定義了幾十種的函數接口,但是剩下的接口都是上面這幾種接口的變種,大多為限制參數類型,數量,下面舉幾個例子。
類型限制接口
參數類型,例如IntPredicate,LongPredicate, DoublePredicate,這幾個接口,都是在基于Predicate接口的,不同的就是他們的泛型類型分别變成了Integer,Long,Double,IntConsumer,LongConsumer, DoubleConsumer比如這幾個,對應的就是Consumer,Consumer,Consumer,其餘的是一樣的道理,就不再舉例子了
傳回值類型,和上面類似,隻是命名的規則上多了一個To,例如IntToDoubleFunction,IntToLongFunction, 很明顯就是對應的Funtion與Fcuntion,其餘同理,另外需要注意的是,參數限制與傳回值限制的命名唯一不同就是To,簡單來說,前面不帶To的都是參數類型限制,帶To的是傳回值類型限制,對于沒有參數的函數接口,那顯而易見隻可能是對傳回值作限制。例如LongFunction就相當于Function而多了一個To的ToLongFunction就相當于Function,也就是對傳回值類型作了限制。
數量限制接口
有些接口需要接受兩名參數,此類接口的所有名字前面都是附加上Bi,是Binary的縮寫,開頭也介紹過這個單詞了,是二進制的意思,例如BiPredicate,BiFcuntion等等,而由于java沒有多傳回值的設定,是以Bi指的都是參數為兩個
Operator接口
此類接口隻有2個分别是UnaryOperator一進制操作符接口,與BinaryOperator二進制操作符接口,這類接口屬于Function接口的簡寫,他們隻有一個泛型參數,意思是Funtion的參數與傳回值類型相同,一般多用于操作計算,計算 a + b的BiFcuntion如果限制條件為Integer的話 往往要這麼寫BiFunction前2個泛型代表參數,最後一個代表傳回值,看起來似乎是有點繁重了,這個時候就可以用BinaryOperator來代替了。
下面是各種類型的接口的示意圖,相信隻要真正了解了,其實問題并不大
關于lambda的限制
Java8中的lambda表達式,并不是完全閉包,lambda表達式對值封閉,不對變量封閉。簡單點來說就是局部變量在lambda表達式中如果要使用,必須是聲明final類型或者是隐式的final例如
intnum = 123;
Consumer print = () -> System.out.println(num);
就是可以的,雖然num沒有被聲明為final,但從整體來看,他和final類型的變量的表現是一緻的,可如果是這樣的代碼
intnum = 123;
num ++;
Consumer print = () -> System.out.println(num);
則無法通過編譯器,這就是對值封閉(也就是棧上的變量封閉)
如果上文中的num是執行個體變量或者是靜态變量就沒有這個限制。
看到這裡,自然而然就會有疑問為什麼會這樣?或者說為什麼要這麼設計。理由有很多,例如函數的不變性,線程安全等等等,這裡我給一個簡單的說明
為什麼局部變量會有限制而靜态變量和全局變量就沒有限制,因為局部變量是儲存在棧上的,而衆所周知,棧上的變量都隐式的表現了它們僅限于它們所在的線程,而靜态變量與執行個體變量是儲存在靜态區與堆中的,而這兩塊區域是線程共享的,是以通路并沒有問題。
現在我們假設如果lambda表達式可以局部變量的情況,執行個體變量存儲在堆中,局部變量存儲在棧上,而lambda表達式是在另外一個線程中使用的,那麼在通路局部變量的時候,因為線程不共享,是以lambda可能會在配置設定該變量的線程将這個變量收回之後,去通路該變量。是以說,Java在通路自由局部變量時,實際上是在通路它的副本,而不是通路原始變量。如果局部變量僅僅指派一次那就沒有什麼差別了。
嚴格保證這種限制會讓你的代碼變得無比安全,如果你學習或了解過一些經典的函數式語言的話,就會知道不變性的重要性,這也是為什麼stream流可以十分友善的改成并行流的重要原因之一。
總結
本篇介紹了四大函數接口和他們引申出的各類接口,終點是對不同種類行為的封裝導緻了設計出不同的函數接口,另外在使用函數接口或者lambda表達式的時候,要注意lambda對值封閉這個特性。
責任編輯: