java8中最大的變化就是引入了lambda表達式,一種緊湊的傳遞行為的方式,這也是本書剩下部分所要討論的內容,讓我們進入其中吧。
編寫第一個lambda表達式
swing是一個平臺無關的gui庫,在該庫中,有很多常見的習慣,比如為了知道用戶點點擊了什麼,註冊一個事件監聽器,這個事件監聽器可以執行一些操作響應用戶的輸入。
在該例子中,我們創建了一個對象實現了ActionListener接口,該接口只有一個方法actionPerformed(),當用戶點擊了按鈕之後,這個方法會被調用,該匿名內部類提供了該方法的實現。
匿名內部類是為了讓java程式設計師傳遞行為和傳遞數據一樣容易,不幸的是,他們並不容易,為了調用處理邏輯的代碼仍然有四行模板代碼,重複的模板代碼並不是唯一的問題,這種代碼也難以閱讀,我們並不想傳遞一個對象,而僅僅只需要傳遞某種行為,在java8中我們可以寫得更簡潔
不同於傳遞一個實現某個接口的對象,我們傳遞了一段沒有命名函數的代碼,
event
是參數,同匿名內部類的參數一樣,
->
將參數和lambda表達式的內容體分開。同匿名內部類做法另外一種不同就是,我們申明變量的方式,之前,我們需要顯示申明類型ActionEvent,在該例中,我們不需要提供類型,編譯也能通過,在這背後發生的是javac從上下文獲取event類型,此處是從addActionListener籤名中獲取,這意味著你不需要顯示申明其類型,我們之後會更加詳細討論這種設計,首先讓我們看看編寫lambda表達式幾種不同的方法。
如何在正確的場合使用lambda表達式?下面有幾個使用lambda表達式的例子
①展示了在沒有參數的情況下如何使用lambda,可以使用一對空的小括號來表示沒有參數,這是一個實現了Runnable的lambda的表達式,該接口只有一個方法run(),該方法不接受任何參數,且返回void.
②展示了只有一個參數的lambda的表達式。不同於只包含一條表達式的lambda。
③包含了一個代碼塊,包含在一個大括號中,代碼塊同平常的方法類似,在結束之前可以返回結果或者拋出異常,
④包含了多個參數,這種情況下有必要考慮如何閱讀這條表達式,這行代碼沒有相加兩個數,它創建了一個函數相加兩個數,add這個變量並不是兩個數的和,而是一個相加兩個數的函數。
⑤目前為止,所有的lambda表達式參數類型都是由編譯器確定,這非常棒,但是有些時候我們需要明確參數類型。
上述所有表達式都表明lambda表達式依賴於上下文,由編譯器去判斷,這種做法也不完全新創的,在example2-4中數組的初始化依賴於上下文環境,還有null,只有把它分配的時候才知道其類型。
Using Values
你以往使用匿名內部類的時候,你可能遇到這種情況,在內部類中你想使用外部的變量,需要把外部變量申明為final類型,變量申明為final意味著不能將該變量指向其他對象。
在java8中,這種限制有所放鬆,可以引用非final的參數,雖然沒有申明為final類型,但是必須當做final類型看待,不能去改變其所指的對象,否則會報錯。
如果多次為一個變量賦值,並且單算在lambda表達式中進行引用,那麼會得到一個變異錯誤。
這也是為什麼人們把lambda表達式當做閉包看待,在程式語言的爭論中,有許多關於java是否有閉包的爭論,因為其只能引用final變量,為了避免這一個無端的爭論,我在本書中將他們稱之為lambda表達式。但是無論我如何稱呼他們,我已經提到過lambda表達式是靜態類型的,因此讓我們研究表達式本身:他們稱之為函數式接口。
函數式接口
函數式接口是指的只有一個抽象方法的接口,被當做是lambda表達式的一種類型使用。在java中,所有的參數都有類型,如果我們傳入3到一個方法中,這個參數是int類型,那麼lambda是什麼類型的呢?
類型討論?
某些情況下,需要提供確切的類型標誌,我的建議是你和你的團隊怎麼易讀怎麼做。有的時候不標明類型讓其看起來更加簡潔,有時候加上類型標誌讓其更加明顯。我發現,起初加上類型可能看起來很有用,但是之後可能你只會在必要的時候才會加上,本章將會提供幾個原則讓你很容易辨別出是否必要加上。
類型的討論其實是java7類型討論的延續。
對於變量oldWordCount我們明確指明了泛型的類型,但是diamonWordCounts使用了diamond operator,泛型類型並沒有指出,編譯器會指定其類型。如果你直接傳遞一個構造函數到方法中,編譯器也能指出泛型類型,同樣的java7允許空出構造函數的泛型,java8允許lambda表達式的參數類型。其實沒有什麼魔法,java編譯器在lambda表達式上下文中尋找參數確定的類型。讓我們通過幾個例子更加深入討論。在下面兩種情況下,我們傳入一個參數到一個函數接口中,因此我們很容易確定兩者的區別。
第一個例子:
不同於之前的ActionListener示例,Predicate是一個有返回值的lambda表達式,這裡表達式的內容為x > 5,表達式的值作為lambda表達式的返回值。我們可以看到Predicate有一個泛型參數,上一個表達式中我們傳入一個int值,java編譯器會檢測返回值是否為一個boolean值。
讓我們看看另外一個更加複雜的例子:
BinaryOperator接口
這個接口有兩個參數以及一個返回值,兩個參數類型相同,在實例中我們使用的參數類型為Long類型,但是如果不提供足夠的信息編譯器就會返回一個變異錯誤,例如在下面的一個表達式中就會拋出變異錯誤
總結:
1.lambda表達式沒有命名,用來像傳遞數據一樣傳遞操作。2.函數接口指的是只有一個抽象方法的接口,被當做是lambda表達式的類型。