您好,登錄后才能下訂單哦!
這篇文章主要介紹“java8的Lambda表達式怎么應用”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“java8的Lambda表達式怎么應用”文章能幫助大家解決問題。
前片文章講到,使用匿名類來表示不同的行為并不令人滿意:代碼十分啰嗦,這會影響程序
員在實踐中使用行為參數化的積極性。在本章中,我們會教給你Java 8中解決這個問題的新工
具——Lambda表達式。它可以讓你很簡潔地表示一個行為或傳遞代碼。現在你可以把Lambda
表達式看作匿名功能,它基本上就是沒有聲明名稱的方法,但和匿名類一樣,它也可以作為參
數傳遞給一個方法。
可以吧Lambda表達式簡單的理解為可以可以傳遞匿名函數的一種形式:它沒有名稱,但是有參數列表,函數主體,返回類型,甚至還可以有一個可以拋出異常的函數列表.
匿名:我們說匿名,是因為它不像普通的方法那樣有一個明確的名稱:寫得少而想得多!
函數: 我們說它是函數,是因為Lambda函數不像方法那樣屬于某個特定的類。但和方法一樣,Lambda有參數列表、函數主體、返回類型,還可能有可以拋出的異常列表。
傳遞:Lambda表達式可以作為參數傳遞給方法或存儲在變量中.
簡潔:無需像匿名類那樣寫很多模版代碼.
在前面的講解中,我們寫過一個簡單的Lambda表達式
可以看到,Lambda表達式有三個部分:
* 參數列表:這里它采用了 Comparator 中 compare 方法的參數,兩個 Apple 。
* 箭頭:把參數和函數主題分開.
* Lambda主體: 比較兩個 Apple 的重量。表達式就是Lambda的返回值了。
為了進一步說明,下面給出了Java 8中五個有效的Lambda表達式的例子。
java語言設計者選擇這樣的語法,是因為C#和Scala等語言中的類似功能廣受歡迎.Lambda的基本語法就是:
(parameters) -> expression
或(請注意語句的花括號)
(parameters) -> { statements; }
一言以蔽之,函數式接口就是只定義一個抽象方法的接口。
如我們前面創建的:
public interface Predicate<T>{ boolean
函數式接口的抽象方法的簽名基本上就是Lambda表達式的簽名,我們將這種抽象方法叫做函數描述符.
這很好理解:因為函數式接口只有一個抽象方法,因此我們只需要知道參數列表,和返回值就可以描述這個函數.
例如, Runnable 接口可以看作一個什么也不接受什么也不返回( void )的函數的
簽名,因為它只有一個叫作 run 的抽象方法,這個方法什么也不接受,什么也不返回( void )。
函數式接口定義且只定義了一個抽象方法.函數式接口很有用,因為抽象方法的簽名可以描述為Lambda表達式的簽名
函數式接口的抽象方法的簽名稱為函數描述符.
Java API中已經有了幾個函數式接口
Java 8的庫設計師幫你在 java.util.function 包中引入了幾個新的函數式接口。我們接下
來會介紹 Predicate 、 Consumer 和 Function ,更完整的可以查看API.
為了總結關于函數式接口和Lambda的討論,表3-3總結了一些使用案例、Lambda的例子,以
及可以使用的函數式接口。
Lambda表達式的類型是從Lambda的上下文中推斷出來的,上下文中Lambda表達式需要的類型稱為目標類型.
舉個上節中的例子:
java編譯器可以從上下文中推斷出用什么函數式接口來配合Lambda表達式,這意味著他可以推斷出適合Lambda表達式的簽名,因為函數描述符也可以從目標類型中得到.
這樣做的好處在于,編譯器可以了解Lambda表達式的參數類型
Lambda表達式有多個參數,代碼可讀性的好處就更為明顯。例如,你可以這樣來創建一個
Comparator 對象:
請注意,有時候顯式寫出類型更易讀,有時候去掉它們更易讀。沒有什么法則說哪種更好;
對于如何讓代碼更易讀,程序員必須做出自己的選擇
Lambda表達式也允許使用自有變量(不是參數,是在外層作用域定義的變量),就想匿名類一樣,他們被稱為捕獲Lambda
int portNumber = 1337; Runnable r = () -> System.out.println(portNumber);
需要注意的是:盡管Lambda可以沒有限制地捕獲(也就是在其主體中引用)實例變量和靜態變量。但是局部變量必須顯示聲明為final,或者事實上就是final.
換句話說:Lambda表達式只能捕獲指派給他們的局部變量一次((注:捕獲實例變量可以被看作捕獲最終局部變量 this 。)),
例如,下面的代碼無法編譯,因為 portNumber
變量被賦值兩次:
你可能會問自己,為什么局部變量有這些限制?????
第一,實例變量和局部變量背后的實現有一個關鍵的不同,實例變量存儲在堆中,局部變量存儲在棧中,
如果Lambda可以直接訪問局
部變量,而且Lambda是在一個線程中使用的,則使用Lambda的線程,可能會在分配該變量的線
程將這個變量收回之后,去訪問該變量。因此,Java在訪問自由局部變量時,實際上是在訪問它
的副本,而不是訪問原始變量。如果局部變量僅僅賦值一次那就沒有什么區別了——因此就有了
這個限制。
第二,這一限制不鼓勵你使用改變外部變量的典型命令式編程模式(我們會在以后的各章中
解釋,這種模式會阻礙很容易做到的并行處理)
閉包:
你可能已經聽說過閉包這個詞,你可能會想Lambda是否滿足閉包的定義.科學的講,閉包就是一個函數的實例.且它可以無限制的訪問那個函數的非本地變量.
例如,閉包可以作為參數傳遞給另一個函數。它也可以訪
問和修改其作用域之外的變量。現在,Java 8的Lambda和匿名類可以做類似于閉包的事情:它們可以作為參數傳遞給方法,并且可以訪問其作用域之外的變量.
但是有一個限制:就是它們不能修改定義Lambda的方法的局部變量的內容.這些變量必須是隱式最終的.可以認為Lambda是對值封閉,而不是對變量封閉.
如前所述: 這種限制的原因在于局部變量保存在棧上,并且隱式表示它們僅限于其所在線程.如果允許捕獲局部變量,就會引發造成線程不安全的新得可能性,而這時我們不想看到的
實例變量是可以的,因為它們保存在堆中,而堆是在線程中共享的.
方法引用可以被看作是調用Lambda表達式的一種快捷方法.
它的基本思想是:
如果一個Lambda表達式代表是 直接調用這個方法,那最好還是用名稱來調用它.
事實上,方法引用就是讓你根據已有的方法來創建Lambda表達式.
它是如何工作的呢?
當你需要使用方法引用時,目標引用放在分隔符 :: 前,方法名放在后面.
例如,
Apple::getWeight 就是引用了 Apple 類中定義的方法 getWeight 。請記住,不需要括號,因為
你沒有實際調用這個方法。方法引用就是Lambda表達式 (Apple a) -> a.getWeight() 的快捷
寫法。
/** * 方法引用 */ @Test public void test5(){ List<Apple> inventory1 = initInventory(); inventory1.sort((Apple a1, Apple a2)-> a1.getWeight().compareTo(a2.getWeight())); System.out.println(inventory1); List<Apple> inventory = initInventory(); inventory.sort(Comparator.comparing(Apple::getWeight)); System.out.println(inventory); }
現在讓我們用一個上節中對蘋果排序的例子貫穿一下我們目前接觸到的所有知識
我們想要實現的最終解決方案是這樣的:
inventory.sort(comparing(Apple::getWeight));
Java 8的API已經為你提供了一個 List 可用的 sort 方法,你不用自己去實現它。
那么最困難的部分已經搞定了!但是,如何把排序策略傳遞給 sort 方法呢?你看, sort 方法的
簽名是這樣的:
void sort(Comparator<? super
它需要一個Comparator對象來比較兩個Apple,這就是zaijava中傳遞策略的方式:他們必須包裹在一個對象里,
我們說 sort 的行為被參數化了:傳遞給它的排序策略不同,其行為也會不同.
你的第一個解決方案看上去是這樣的:
public class AppleComparator implements Comparator<Apple> { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } } inventory.sort(new
你可以使用匿名類來改進解決方案,而不是實現一個 Comparator 卻只實
例化一次:
inventory.sort(new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return
但你的解決方案仍然挺啰嗦的。Java 8引入了Lambda表達式,它提供了一種輕量級語法來實
現相同的目標:傳遞代碼
inventory.sort((Apple a1, Apple a2)-> a1.getWeight().compareTo(a2.getWeight()));
我們前面解釋過了,Java編譯器可以根據Lambda出現的上下文來推斷Lambda表達式參數的
類型。那么你的解決方案就可以重寫成這樣:
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
你的代碼還能變得更易讀一點嗎???
Comparator 具有一個叫做Comparing的靜態輔助方法,方法描述如下:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor) { Objects.requireNonNull(keyExtractor); return
可以看到,它可以接受一個Function來提取一個鍵值,并生成一個Compartor對象.
它可以像下面這樣用(注意你現在傳遞的Lambda只有一
個參數:Lambda說明了如何從蘋果中提取需要比較的鍵值):
Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());
因此,現在你可以該代碼改為這樣:
import static
方法引用就是替代那些轉發參數的Lambda表達式的語法糖.
你可以使用方法引用讓你的代碼更簡潔,假設你靜態導入了 java.util.Comparator.comparing.
那么你現在的代碼可以這樣:
inventory.sort(comparing(Apple::getWeight));
關于“java8的Lambda表達式怎么應用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。