Java8中函數接口有不少,大概有幾十個吧,具體到底是多少我也數不清,因此一開始看的時候感受一臉懵逼,不過其實根本沒那麼複雜,畢竟不該該也不必把一個東西設計的很複雜。java
在學習瞭解以前,但願你們能記住幾個單詞,掌握這幾個單詞,什麼3,40個官方的函數接口都是小問題了,不信的話接着往下看啦。ok,那這幾個單詞呢分別是supplier 提供者
,consumer 消費者
,function 函數
,operation 運算符
,binary 二元(就是數學裏二元一次方程那個二元,表明2個的意思),雙重的
安全
函數接口,你能夠理解爲對一段行爲的抽象,簡單點說能夠在方法就是將一段行爲做爲參數進行傳遞,這個行爲呢,能夠是一段代碼,也能夠是一個方法,那你能夠想象在java8以前要將一段方法做爲參數傳遞只能經過匿名內部類來實現,並且代碼很難看,也很長,函數接口就是對匿名內部類的優化。
雖然類庫中的基本函數接口特別多,但其實整體能夠分紅四類,就好像阿拉伯數字是無限多的,但總共就10個基本數字同樣,理解了這4個,其餘的就都明白了。閉包
function,顧名思義,函數的意思,這裏的函數是指數學上的函數哦,你也能夠說是嚴格函數語言中的函數,例如haskell裏的,他接受一個參數,返回一個值,永遠都是這樣,是一個恆定的,狀態不可改變的方法。其實想講函數這個完全將明白能夠再開一篇博客了,因此這裏不詳細的說了。
上面說到,函數接口是對行爲的抽象,所以我方便你們理解,就用java中的方法做例子。app
Fcuntion接口是對接受一個T類型參數,返回R類型的結果的方法的抽象,經過調用apply方法執行內容。函數
public class Operation{ /* 下面這個方法接受一個int類型參數a,返回a+1,符合我上面說的接受一個參數,返回一個值 因此呢這個方法就符合Function接口的定義,那要怎麼用呢,繼續看例子 */ public static final int addOne(int a){ return a+1; } /* 該方法第二個參數接受一個function類型的行爲,而後調用apply,對a執行這段行爲 */ public static int oper(int a, Function<Integer,Integer> action){ return action.apply(a); } /* 下面調用這個oper方法,將addOne方法做爲參數傳遞 */ pulic static void main(String[] args){ int x = 1; int y = oper(x,x -> addOne(x));//這裏能夠換成方法引用的寫法 int y = oper(x,Operation::addOne) System.out.printf("x= %d, y = %d", x, y); // 打印結果 x=1, y=2 /* 固然你也可使用lambda表達式來表示這段行爲,只要保證一個參數,一個返回值就能匹配 */ y = oper(x, x -> x + 3 ); // y = 4 y = oper(x, x -> x * 3 ); // y = 3 } }
這裏的箭頭指向的位置就是形參,能夠看到第二個箭頭的Lambda表達式指向了Funtion接口學習
Consumer 接口翻譯過來就是消費者,顧名思義,該接口對應的方法類型爲接收一個參數,沒有返回值,能夠通俗的理解成將這個參數'消費掉了',通常來講使用Consumer接口每每伴隨着一些指望狀態的改變或者事件的發生,例如最典型的forEach就是使用的Consumer接口,雖然沒有任何的返回值,可是卻向控制檯輸出了語句。
Consumer 使用accept對參數執行行爲優化
public static void main(String[] args) { Consumer<String> printString = s -> System.out.println(s); printString.accept("helloWorld!"); //控制檯輸出 helloWorld! }
Supplier 接口翻譯過來就是提供者,和上面的消費者相反,該接口對應的方法類型爲不接受參數,可是提供一個返回值,通俗的理解爲這種接口是無私的奉獻者,不只不要參數,還返回一個值,使用get()方法得到這個返回值spa
Supplier<String> getInstance = () -> "HelloWorld!"; System.out.println(getInstance.get()); // 控偶值臺輸出 HelloWorld
predicate<t,boolean> 謂語接口,顧名思義,中文中的‘是’與‘不是’是中文語法的謂語,一樣的該接口對應的方法爲接收一個參數,返回一個Boolean類型值,多用於判斷與過濾,固然你能夠把他理解成特殊的Funcation<t,r>,可是爲了便於區分語義,仍是單獨的劃了一個接口,使用test()方法執行這段行爲線程
public static void main(String[] args) { Predicate<Integer> predOdd = integer -> integer % 2 == 1; System.out.println(predOdd.test(5)); //控制檯輸出 5 }
介紹完正面這四種最基本的接口,剩餘的接口就能夠很容易的理解了,java8中定義了幾十種的函數接口,可是剩下的接口都是上面這幾種接口的變種,大多爲限制參數類型,數量,下面舉幾個例子。翻譯
IntPredicate,LongPredicate, DoublePredicate
,這幾個接口,都是在基於Predicate接口的,不一樣的就是他們的泛型類型分別變成了Integer,Long,Double,IntConsumer,LongConsumer, DoubleConsumer
好比這幾個,對應的就是Consumer<Integer>,Consumer<Long>,Consumer<Double>
,其他的是同樣的道理,就再也不舉例子了IntToDoubleFunction,IntToLongFunction,
很明顯就是對應的Funtion<Integer,Double>
與Fcuntion<Integer,Long>
,其他同理,另外須要注意的是,參數限制與返回值限制的命名惟一不一樣就是To,簡單來講,前面不帶To的都是參數類型限制,帶To的是返回值類型限制,對於沒有參數的函數接口,那顯而易見只多是對返回值做限制。例如LongFunction<R>
就至關於Function<Long,R>
而多了一個To的ToLongFunction<T>
就至關於Function<T,Long>
,也就是對返回值類型做了限制。Binary
的縮寫,開頭也介紹過這個單詞了,是二元的意思,例如BiPredicate,BiFcuntion
等等,而因爲java沒有多返回值的設定,因此Bi指的都是參數爲兩個UnaryOperator<T>
一元操做符接口,與BinaryOperator<T>
二元操做符接口,這類接口屬於Function接口的簡寫,他們只有一個泛型參數,意思是Funtion的參數與返回值類型相同,通常多用於操做計算,計算 a + b的BiFcuntion若是限制條件爲Integer的話 每每要這麼寫BiFunction<Integer,Integer,Integer>
前2個泛型表明參數,最後一個表明返回值,看起來彷佛是有點繁重了,這個時候就能夠用BinaryOperator<Integer>
來代替了。下面是各類類型的接口的示意圖,相信只要真正理解了,其實問題並不大
Java8中的lambda表達式,並非徹底閉包,lambda表達式對值封閉,不對變量封閉。簡單點來講就是局部變量在lambda表達式中若是要使用,必須是聲明final類型或者是隱式的final例如
int num = 123; Consumer<Integer> print = () -> System.out.println(num);
就是能夠的,雖然num沒有被聲明爲final,但從總體來看,他和final類型的變量的表現是一致的,可若是是這樣的代碼
int num = 123; num ++; Consumer<Integer> print = () -> System.out.println(num);
則沒法經過編譯器,這就是對值封閉(也就是棧上的變量封閉)
若是上文中的num是實例變量或者是靜態變量就沒有這個限制。
看到這裏,天然而然就會有疑問爲何會這樣?或者說爲何要這麼設計。理由有不少,例如函數的不變性,線程安全等等等,這裏我給一個簡單的說明
本篇介紹了四大函數接口和他們引伸出的各種接口,終點是對不一樣種類行爲的封裝致使了設計出不一樣的函數接口,另外在使用函數接口或者lambda表達式的時候,要注意lambda對值封閉這個特性。