1. 程式人生 > >[work] Lambda 表示式有何用處

[work] Lambda 表示式有何用處

作者:Sevenvidia
連結:https://www.zhihu.com/question/20125256/answer/324121308
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
 

對Java比較熟悉,就用Java來講一講

什麼是Lambda?

我們知道,對於一個Java變數,我們可以賦給其一個“值”

如果你想把“一塊程式碼”賦給一個Java變數,應該怎麼做呢?

比如,我想把右邊那塊程式碼,賦給一個叫做aBlockOfCode的Java變數:

在Java 8之前,這個是做不到的。但是Java 8問世之後,利用Lambda特性,就可以做到了。

當然,這個並不是一個很簡潔的寫法。所以,為了使這個賦值操作更加elegant, 我們可以移除一些沒用的宣告。

這樣,我們就成功的非常優雅的把“一塊程式碼”賦給了一個變數。而“這塊程式碼”,或者說“這個被賦給一個變數的函式”,就是一個Lambda表示式

但是這裡仍然有一個問題,就是變數aBlockOfCode的型別應該是什麼?

在Java 8裡面,所有的Lambda的型別都是一個介面,而Lambda表示式本身,也就是”那段程式碼“,需要是這個介面的實現。這是我認為理解Lambda的一個關鍵所在,簡而言之就是,Lambda表示式本身就是一個介面的實現。直接這樣說可能還是有點讓人困擾,我們繼續看看例子。我們給上面的aBlockOfCode加上一個型別:

這種只有一個介面函式需要被實現的介面型別,我們叫它”函式式介面“。為了避免後來的人在這個介面中增加介面函式導致其有多個介面函式需要被實現,變成"非函式介面”,我們可以在這個上面加上一個宣告@FunctionalInterface, 這樣別人就無法在裡面新增新的介面函數了:

這樣,我們就得到了一個完整的Lambda表示式宣告:

Lambda表示式有什麼作用?

最直觀的作用就是使得程式碼變得異常簡潔。

我們可以對比一下Lambda表示式和傳統的Java對同一個介面的實現:

這兩種寫法本質上是等價的。但是顯然,Java 8中的寫法更加優雅簡潔。並且,由於Lambda可以直接賦值給一個變數,我們就可以直接把Lambda作為引數傳給函式, 而傳統的Java必須有明確的介面實現的定義,初始化才行:

有些情況下,這個介面實現只需要用到一次。傳統的Java 7必須要求你定義一個“汙染環境”的介面實現MyInterfaceImpl,而相較之下Java 8的Lambda, 就顯得乾淨很多。

Lambda結合FunctionalInterface Lib, forEach, stream(),method reference等新特性可以使程式碼變的更加簡潔!

直接上例子。

假設Person的定義和List<Person>的值都給定。

現在需要你打印出guiltyPersons List裡面所有LastName以"Z"開頭的人的FirstName。

原生態Lambda寫法:定義兩個函式式介面,定義一個靜態函式,呼叫靜態函式並給引數賦值Lambda表示式。

這個程式碼實際上已經比較簡潔了,但是我們還可以更簡潔麼?

當然可以。在Java 8中有一個函式式介面的包,裡面定義了大量可能用到的函式式介面(java.util.function (Java Platform SE 8 ))。所以,我們在這裡壓根都不需要定義NameChecker和Executor這兩個函式式介面,直接用Java 8函式式介面包裡的Predicate<T>和Consumer<T>就可以了——因為他們這一對的介面定義和NameChecker/Executor其實是一樣的。

第一步簡化 - 利用函式式介面包:

靜態函式裡面的for each迴圈其實是非常礙眼的。這裡可以利用Iterable自帶的forEach()來替代。forEach()本身可以接受一個Consumer<T> 引數。

第二步簡化 - 用Iterable.forEach()取代foreach loop:

由於靜態函式其實只是對List進行了一通操作,這裡我們可以甩掉靜態函式,直接使用stream()特性來完成。stream()的幾個方法都是接受Predicate<T>,Consumer<T>等引數的(java.util.stream (Java Platform SE 8 ))。你理解了上面的內容,stream()這裡就非常好理解了,並不需要多做解釋。

第三步簡化 - 利用stream()替代靜態函式:

對比最開始的Lambda寫法,這裡已經非常非常簡潔了。但是如果,我們的要求變一下,變成print這個人的全部資訊,及p -> System.out.println(p); 那麼還可以利用Method reference來繼續簡化。所謂Method reference, 就是用已經寫好的別的Object/Class的method來代替Lambda expression。格式如下:

第四步簡化 - 如果是println(p),則可以利用Method reference代替forEach中的Lambda表示式:

這基本上就是能寫的最簡潔的版本了。

Lambda配合Optional<T>可以使Java對於null的處理變的異常優雅

這裡假設我們有一個person object,以及一個person object的Optional wrapper:

Optional<T>如果不結合Lambda使用的話,並不能使原來繁瑣的null check變的簡單。

只有當Optional<T>結合Lambda一起使用的時候,才能發揮出其真正的威力!

我們現在就來對比一下下面四種常見的null處理中,Java 8的Lambda+Optional<T>和傳統Java兩者之間對於null的處理差異。

情況一 - 存在則開幹

情況二 - 存在則返回,無則返回屁

情況三 - 存在則返回,無則由函式產生

情況四 - 奪命連環null檢查

由上述四種情況可以清楚地看到,Optional<T>+Lambda可以讓我們少寫很多ifElse塊。尤其是對於情況四那種奪命連環null檢查,傳統java的寫法顯得冗長難懂,而新的Optional<T>+Lambda則清新脫俗,清楚簡潔。

關於Java的Lambda, 還有東西需要討論和學習。比如如何handle lambda exception,如何利用Lambda的特性來進行parallel processing等。總之,我只是一如既往地介紹個大概,讓你大概知道,哦!原來是這樣子就OK了。網上關於Lambda有很多相關的教程,多看多練。假以時日,必定有所精益。

 

之二:

作者:濤吳
連結:https://www.zhihu.com/question/20125256/answer/14058285
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
 

簡單來說,程式設計中提到的 lambda 表示式,通常是在需要一個函式,但是又不想費神去命名一個函式的場合下使用,也就是指匿名函式。這一用法跟所謂 λ 演算(題目說明裡的維基連結)的關係,有點像原子彈和質能方程的關係,差別其實還是挺大的。

不談形式化的 λ 演算,只說有實際用途的匿名函式。先舉一個普通的 Python 例子:將一個 list 裡的每個元素都平方:

map( lambda x: x*x, [y for y in range(10)] )

這個寫法要好過

def sq(x):
    return x * x

map(sq, [y for y in range(10)])

,因為後者多定義了一個(汙染環境的)函式,尤其如果這個函式只會使用一次的話。而且第一種寫法實際上更易讀,因為那個對映到列表上的函式具體是要做什麼,非常一目瞭然。如果你仔細觀察自己的程式碼,會發現這種場景其實很常見:你在某處就真的只需要一個能做一件事情的函式而已,連它叫什麼名字都無關緊要。Lambda 表示式就可以用來做這件事。

進一步講,匿名函式本質上就是一個函式,它所抽象出來的東西是一組運算。這是什麼意思呢?類比

a = [1, 2, 3]

f = lambda x : x + 1

,你會發現,等號右邊的東西完全可以脫離等號左邊的東西而存在,等號左邊的名字只是右邊之實體的識別符號。如果你能習慣 [1, 2, 3] 單獨存在,那麼 lambda x : x + 1 也能單獨存在其實也就不難理解了,它的意義就是給「某個數加一」這一運算本身。

現在回頭來看 map() 函式,它可以將一個函式對映到一個可列舉型別上面。沿用上面給出的 a 和 f,可以寫:

map(f, a)

也就是將函式 f 依次套用在 a 的每一個元素上面,獲得結果 [2, 3, 4]。現在用 lambda 表示式來替換 f,就變成:

map( lambda x : x + 1, [1, 2, 3] )

會不會覺得現在很一目瞭然了?尤其是類比

a = [1, 2, 3]
r = []
for each in a:
    r.append(each+1)

這樣的寫法時,你會發現自己如果能將「遍歷列表,給遇到的每個元素都做某種運算」的過程從一個迴圈裡抽象出來成為一個函式 map,然後用 lambda 表示式將這種運算作為引數傳給 map 的話,考慮事情的思維層級會高出一些來,需要顧及的細節也少了一點。Python 之中,類似能用到 lambda 表示式的「高階」函式還有 reduce、filter 等等,很多語言也都有這樣的工具(不過這些特性最好不要在 Python 中用太多,原因詳見 http://www.zhihu.com/question/19794855/answer/12987428 的評論部分)。這種能夠接受一個函式作為引數的函式叫做「高階函式」(higher-order function),是來自函數語言程式設計(functional programming)的思想。

和其他很多語言相比,Python 的 lambda 限制多多,最嚴重的當屬它只能由一條表示式組成。這個限制主要是為了防止濫用,因為當人們發覺 lambda 很方便,就比較容易濫用,可是用多了會讓程式看起來不那麼清晰,畢竟每個人對於抽象層級的忍耐 / 理解程度都有所不同。