1. Lambda
咱們首先來說說 Lambda 這個名字,Lambda 并不是一個什么的縮寫,它是希臘第十一個字母 λ 的讀音,同時它也是微積分函數中的一個概念,所表達的意思是一個函數入參和出參定義,在編程語言中其實是借用了數學中的 λ,并且多了一點含義,在編程語言中功能代表它具體功能的叫法是匿名函數(Anonymous Function),根據百科的解釋:
匿名函數(英語:Anonymous Function)在計算機編程中是指一類無需定義標識符(函數名)的函數或子程序。
到這我們應該看懂了,在編程語言中引入了 λ 的數學中的意思后,還加入了“匿名”這個概念,為什么要加它呢?顯然是為了讓開發(fā)者寫起來更加方便,不必去想具體的函數名,尤其是在流式表達中,匿名能讓你更加高效。
接著再來說說Lambda 的歷史,雖然它在 JDK8 發(fā)布之后才正式出現,但是在編程語言界,它是一個具有悠久歷史的東西,最早在 1958 年在Lisp 語言中首先采用,而且雖然Java脫胎于C++,但是C++在2011年已經發(fā)布了Lambda 了,但是 JDK8 的 LTS 在2014年才發(fā)布,所以 Java 被人叫做老土不是沒有原因的,現代編程語言則是全部一出生就自帶 Lambda 支持,所以Lambda 其實是越來越火的一個節(jié)奏~
那么Lambda 到底好在哪?不用寫函數名?其實我覺得要回答這個問題首先要明白Lambda 在編程語言方面到底是什么?
上面也說了,Lambda 在編程語言中往往是一個匿名函數,也就是說Lambda 是一個抽象概念,而編程語言提供了配套支持,比如在 Java 中其實為Lambda 進行配套的就是函數式接口,通過函數式接口生成匿名類和方法進行Lambda 式的處理。
那么,既然是這一套規(guī)則我們明白了,那么Lambda 所提供的好處在Java中就是函數式接口所提供的能力了,函數式接口往往則是提供了一些通用能力,這些函數式接口在JDK中也有一套完整的實踐,那就是 Stream。
Stream 提供了一套完整的流式處理方法幫助我們進行流式調用,熟悉Stream 的讀者應該知道使用它能帶來多么大的便捷,更多關于 Stream 的知識可以看我的 延遲執(zhí)行與不可變,系統(tǒng)講解JavaStream數據處理 ,在這篇文章中有著詳細的敘述。
那么總結起來,Lambda 在Java中所提供的好處就是使用函數式接口對一些問題進行了抽象,從而得到了一些通用能力,這些通用能力就是使用Lambda 最大的好處,下面將會具體講解JDK中都定義了哪些通用能力,看到這的小伙伴可以給本文點個贊,以示鼓勵。
2. 函數式接口
在 Java 中,所有的函數式接口都是以 @Functionallnterface 進行標注的,就像這樣:
@FunctionalInterfacepublic interface Runnable { public abstract void run();}復制代碼
在一個接口上打上 @Functionallnterface并且定義一個抽象方法,這樣的類我們就稱之為函數式接口,當然這個方法并不一定非要用抽象關鍵字來修飾,比如:
@FunctionalInterfacepublic interface Consumer { void accept(T t);}復制代碼
當然,不寫 @Functionallnterface 注解其實也沒關系,但是需要保證,這個接口只定義了一個抽象方法,接口的默認方法不算,那個可以稱得上是接口的靜態(tài)方法了。
為什么只能有一個抽象方法呢?因為你的自定義邏輯就是這個方法的匿名函數,最終會調用這個方法,所以只能有一個。
然后你就可以使用 Lambda 表達式來進行書寫了,就像這樣:
Thread thread = new Thread(() -> { });復制代碼
看吧,很方便的寫法就定義了一個Runnable 的匿名子類出來,不過 Runnable 這種使用Lambda 只是為了生成一個匿名子類的情況確實無法完全發(fā)揮Lambda 的作用,Lambda 更大的作用還是在解決具體的問題上,而非創(chuàng)造一個匿名類。
舉個例子,假如你想定義一個對商品數據進行商品篩查的函數,那么它可能是這樣的:
public List filter(List list, String type) { List result = new ArrayList(); for (Goods goods : list) { if (goods.getType().equals(type)) { result.add(goods); } } return result; }復制代碼
ok,看起來一切沒問題,但是架不住需求改變啊,很快你又需要定義一個對商品金額進行篩查的方法,那么它可能是這樣的:
public List gt(List list, Integer price) { List result = new ArrayList(); for (Goods goods : list) { if (goods.getPrice() > price) { result.add(goods); } } return result; }復制代碼
那么你可以發(fā)現:大部分代碼基本沒變,只有入參和判斷邏輯發(fā)生了一點改變,這個時候你可能會想,能不能把判斷邏輯直接抽象成一個匿名函數,每次只需要簡單寫一個這個判斷函數即可,再把方法入參封裝成一個東西,在任何場景下都可以使用。
看到這,你可能就有點明白了,因為在Java8 已經提供了Stream流去做這件事,上面這個場景其實對應的是Stream 中的filter 方法,而filter 方法的入參就是一個函數式接口——Predicate。
還沒明白嗎?那我說的再清楚一點,Predicate 抽象了判斷這個場景,而且這種抽象不局限于業(yè)務,是直接對某一類場景進行抽象,比如篩選商品類別,篩選商品大于某個金額或者小于某個金額,它不在糾結你到底想要怎么篩選,而是直接對篩選函數進行抽象,得到了Predicate,你想怎么篩選你自己寫,剩下的交給它,它將一勞永逸的解決這類問題,當然這里面還有一部分 Stream 的功勞,不過主要思想還是 Predicate 在做,Stream 這里我們暫且不提。
像這種對于某個場景進行頂級抽象的函數式接口,JDK一共提供了四個:
接下來我將一一為大家進行講述,除了這四個之外還有大量的衍生函數式接口,在JDK8中就有50個左右,不過都是在這四個基礎上進行修改,不必擔心記不住的問題。
3. Consumer
**Consumer **通過名字可以看出它是一個消費函數式接口,主要針對的是消費這個場景,它的代碼定義如下:
@FunctionalInterfacepublic interface Consumer { void accept(T t);}復制代碼
通過泛型 T 定義了一個入參,但是沒有返回值,它代表你可以針對這個入參做一些自定義邏輯,比較典型的例子是 Stream 中的 forEach 方法。
而我們的主要使用場景也往往是循環(huán)進行某項操作,比如有一堆手機號,循環(huán)進行發(fā)短信。
所以消費場景是 Consumer 的主要用武之地,但是有時候你還面臨一個問題,一個入參似乎太少了,有時候你需要對兩個對象進行操作,又懶得將它們合并成一個對象,這種情況 JDK 提供了 BiConsumer:
@FunctionalInterfacepublic interface BiConsumer { void accept(T t, U u);}復制代碼
這種你可以直接傳進去兩個參數了,什么?你想要三個參數的?那沒有,三個或者三個以上我感覺就有必要合并成一個對象進行消費了。
除了這兩個之外,還有DoubleConsumer、IntConsumer和LongConsumer這種限定了入參類型的 Consumer,這里不再多述。
4. Supplier
Supplier通過名字比較難看出來它是一個場景的函數式接口,它主要針對的是get這個場景或者說獲取這個場景,它的代碼定義如下:
@FunctionalInterfacepublic interface Supplier { T get();}復制代碼
通過泛型 T 定義了一個返回值類型,但是沒有入參,它代表你可以針對調用方獲取某個值,比較典型的例子是 Stream 中的 collect 方法,通過自定義傳入我們想要取得的某種對象進行對象收集。
而我們的主要使用場景也往往是收集和聚合這個場景了,這個場景我們也是對獲得這個場景進行收集。
和Consumer一樣,Supplier還具有以下衍生接口:
都是提前對獲取的定義好了數據類型,思想一致,這里不再多述。
5. Predicate
Predicate前文我們已經介紹過,它主要針對的是判斷這個場景,它的代碼定義如下:
@FunctionalInterfacepublic interface Predicate { boolean test(T t);}復制代碼
通過泛型 T 定義了一個入參,返回了一個布爾值,它代表你可以傳入一段判斷邏輯的函數,比較典型的例子是 Stream 中的 filter方法。
我們對于它的使用場景實在是太多了,基本上做任何業(yè)務都有在內存中進行篩選 or 判斷的場景。
所以判斷和篩選場景是 Predicate的主要用武之地,但是有時候你還面臨和上面一樣的問題,一個入參似乎太少了,有時候你需要對兩個對象進行操作,又懶得將它們合并成一個對象,這種情況 JDK 提供了 BiPredicate:
@FunctionalInterfacepublic interface BiPredicate { boolean test(T t, U u);}復制代碼
這種你可以直接傳進去兩個參數進行函數的自定義邏輯。
除了這兩個之外,還有DoublePredicate、IntPredicate和LongPredicate這種限定了入參類型的Predicate,這里不再多述。
6. Function
Function 接口的名字不太能輕易看出來它的場景,它主要針對的則是 轉換這個場景,其實說轉換可能也不太正確,它是一個覆蓋范圍比較廣的場景,你也可以理解為擴展版的Consumer,接口定義如下:
@FunctionalInterfacepublic interface Function { R apply(T t);}復制代碼
通過一個入參 T 進行自定義邏輯處理,最終得到一個出參 R,比較典型的例子是 Stream 中的 map 系列方法和 reduce 系列方法。
為什么我說也可以理解為一個擴展版的Consumer呢?我們還舉例手機號發(fā)短信的場景好了,你通過循環(huán)發(fā)完短信之后可能想拿到發(fā)完短信之后的結果對象,來進行后續(xù)處理。
這個時候單純的Consumer就不行了,因為它沒有返回值,你就可以通過 Function 這種函數式對象進行處理了。
和 Consumer 一樣,Function 也有一個衍生接口可以通過兩個入參返回一個對象——BiFunction。
還有一些定義好了入參和出參的 Function