《Java 8 in Action》Chapter 3:Lambda表達式

来源:https://www.cnblogs.com/HelloDeveloper/archive/2019/08/24/11405581.html
-Advertisement-
Play Games

1. Lambda簡介 可以把Lambda表達式理解為簡潔地表示可傳遞的匿名函數的一種方式:它沒有名稱,但它有參數列表、函數主體、返回類型,可能還有一個可以拋出的異常列表。 匿名——我們說匿名,是因為它不像普通的方法那樣有一個明確的名稱:寫得少而想得多! 函數——我們說它是函數,是因為Lambda函 ...


1. Lambda簡介

可以把Lambda表達式理解為簡潔地表示可傳遞的匿名函數的一種方式:它沒有名稱,但它有參數列表、函數主體、返回類型,可能還有一個可以拋出的異常列表。

  • 匿名——我們說匿名,是因為它不像普通的方法那樣有一個明確的名稱:寫得少而想得多!
  • 函數——我們說它是函數,是因為Lambda函數不像方法那樣屬於某個特定的類。但和方法一樣,Lambda有參數列表、函數主體、返回類型,還可能有可以拋出的異常列表。
  • 傳遞——Lambda表達式可以作為參數傳遞給方法或存儲在變數中。
  • 簡潔——無需像匿名類那樣寫很多模板代碼。

2. Lambda寫法

(parameters) -> expression 或 (parameters) -> { statements; }
eg:(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Lambda表達式有三個部分:

  • 參數列表——這裡它採用了Comparator中compare方法的參數,兩個Apple。
  • 箭頭——箭頭->把參數列表與Lambda主體分隔開。
  • Lambda主體——比較兩個Apple的重量。表達式就是Lambda的返回值了。

3. 函數式介面和函數描述符

函數式介面就是只定義一個抽象方法的介面。介面上標有@FunctionalInterface表示該介面會設計成 一個函數式介面,如果你用@FunctionalInterface定義了一個介面,而它卻不是函數式介面的話,編譯器將返回一個提示原因的錯誤。介面現在還可以擁有預設方法(即在類沒有對方法進行實現時, 其主體為方法提供預設實現的方法)。哪怕有很多預設方法,只要介面只定義了一個抽象方法,它就仍然是一個函數式介面。

函數式介面的抽象方法的簽名就是Lambda表達式的簽名。我們將這種抽象方法叫作:函數描述符。例如,Runnable介面可以看作一個什麼也不接受什麼也不返回(void)的函數的簽名,因為它只有一個叫作run的抽象方法,這個方法什麼也不接受,什麼也不返回(void)。

4. 三種常用的函數式介面

4.1 Predicate

/**
 * Represents a predicate (boolean-valued function) of one argument.
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 * @param <T> the type of the input to the predicate
 * @since 1.8
 */
@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

Predicate的英文示意是:謂詞。
Predicate介面定義了一個名叫test的抽象方法,它接受泛型T對象,並返回一個boolean。

4.2 Consumer

/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
* @param <T> the type of the input to the operation
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
   /**
    * Performs this operation on the given argument.
    * @param t the input argument
    */
   void accept(T t);
}

Consumer的英文示意是:消費者。
Consumer介面定義了一個名叫accept的抽象方法,它接受泛型T對象,並沒有返回任何值。

4.3 Function

/**
* Represents a function that accepts one argument and produces a result.
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object)}.
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
   /**
    * Applies this function to the given argument.
    * @param t the function argument
    * @return the function result
    */
   R apply(T t);
}

Function的英文示意是:功能。
Function介面定義了一個名叫apply的抽象方法,它接受泛型T對象,並返回一個泛型R的對象。

Java還有一個自動裝箱機制來幫助程式員執行這一任務:裝箱和拆箱操作是自動完成的。但這在性能方面是要付出代價的。裝箱後的值本質上就是把原始類型包裹起來,並保存在堆里。因此,裝箱後的值需要更多的記憶體,並需要額外的記憶體搜索來獲取被包裹的原始值。Java 8為我們前面所說的函數式介面帶來了一個專門的版本,以便在輸入和輸出都是原始類型時避免自動裝箱的操作。

4.4 Java 8中的常用函數式介面

5. 類型檢查、類型推斷以及限制

5.1 類型檢查

Lambda的類型是從使用Lambda的上下文推斷出來的。上下文(比如,接受它傳遞的方法的參數,或接受它的值的局部變數)中Lambda表達式需要的類型稱為目標類型。

類型檢查過程可以分解為如下所示。

  • 首先,你要找出filter方法的聲明。
  • 第二,要求它是Predicate(目標類型)對象的第二個正式參數。
  • 第三,Predicate是一個函數式介面,定義了一個叫作test的抽象方法。
  • 第四,test方法描述了一個函數描述符,它可以接受一個Apple,並返回一個boolean.
  • 最後,filter的任何實際參數都必須匹配這個要求。

這段代碼是有效的,因為我們所傳遞的Lambda表達式也同樣接受Apple為參數,並返回一個 boolean。請註意,如果Lambda表達式拋出一個異常,那麼抽象方法所聲明的throws語句也必 須與之匹配。有了目標類型的概念,同一個Lambda表達式就可以與不同的函數式介面聯繫起來,只要它 們的抽象方法簽名能夠相容。比如,前面提到的Callable和PrivilegedAction,這兩個介面都代表著什麼也不接受且返回一個泛型T的函數。 因此,下麵兩個賦值是有效的:

Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;

特殊的void相容規則
如果一個Lambda的主體是一個語句表達式, 它就和一個返回void的函數描述符相容(當然需要參數列表也相容)。
例如,以下兩行都是合法的,儘管List的add方法返回了一個 boolean,而不是Consumer上下文(T -> void)所要求的void:

// Predicate返回了一個boolean 
Predicate<String> p = s -> list.add(s); 
// Consumer返回了一個void 
Consumer<String> b = s -> list.add(s);

5.2 類型推斷

Java編譯器會從上下文(目標類型)推斷出用什麼函數式接 口來配合Lambda表達式,這意味著它也可以推斷出適合Lambda的簽名,因為函數描述符可以通過目標類型來得到。這樣做的好處在於,編譯器可以瞭解Lambda表達式的參數類型,這樣就可以在Lambda語法中省去標註參數類型。

// 沒有類 型推斷
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 
// 有類型推斷
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); 

5.3 使用局部變數

Lambda表達式 也允許使用自由變數(不是參數,而是在外層作用域中定義的變數),就像匿名類一樣。 它們被 稱作捕獲Lambda。
Lambda捕獲的局部變數必須顯式聲明為final, 或事實上是final。換句話說,Lambda表達式只能捕獲指派給它們的局部變數一次。

  • 第一,實例變數和局部變數背後的實現有一 個關鍵不同。實例變數都存儲在堆中,而局部變數則保存在棧上。如果Lambda可以直接訪問局部變數,而且Lambda是在一個線程中使用的,則使用Lambda的線程,可能會在分配該變數的線程將這個變數收回之後,去訪問該變數。因此,Java在訪問自由局部變數時,實際上是在訪問它的副本,而不是訪問原始變數。如果局部變數僅僅賦值一次那就沒有什麼區別了——因此就有了這個限制。
  • 第二,這一限制不鼓勵你使用改變外部變數的典型命令式編程模式(我們會在以後的各章中 解釋,這種模式會阻礙很容易做到的並行處理)。

6. 方法引用

方法引用可以被看作僅僅調用特定方法的Lambda的一種快捷 寫法,方法引用看作針對僅僅涉及單一方法的Lambda的語法糖。目標引用放在分隔符::前,方法的名稱放在後面。方法引用主要有三類:

  • (1) 指向靜態方法的方法引用(例如Integer的parseInt方法,寫作Integer::parseInt)。
  • (2) 指向任意類型實例方法的方法引用(例如String的length方法,寫作 String::length)。
  • (3) 指向現有對象的實例方法的方法引用(假設你有一個局部變數expensiveTransaction 用於存放Transaction類型的對象,它支持實例方法getValue,那麼你就可以寫expensive- Transaction::getValue)。
對於一個現有構造函數,你可以利用它的名稱和關鍵字new來創建它的一個引用: ClassName::new。它的功能與指向靜態方法的引用類似。
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
     
這就等價於:
Supplier<Apple> c1 = () -> new Apple(); // 利用預設構造函數創建 Apple的Lambda表達式
Apple a1 = c1.get(); // 調用Supplier的get方法 將產生一個新的Apple

7. 複合Lambda表達式的有用方法

7.1 比較器複合

Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
// 逆序 按重量遞 減排序
inventory.sort(comparing(Apple::getWeight).reversed());
// 比較器鏈 按重量遞減排序;兩個蘋果一樣重時,進一步按國家排序
inventory.sort(comparing(Apple::getWeight)
         .reversed()
         .thenComparing(Apple::getCountry));

7.2 謂詞複合

// 產生現有Predicate 對象redApple的非
Predicate<Apple> notRedApple = redApple.negate();
// 鏈接兩個謂詞來生成另 一個Predicate對象  一個蘋果既是紅色又比較重
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
// 鏈接Predicate的方法來構造更複雜Predicate對象 表達要麼是重(150克以上)的紅蘋果,要麼是綠蘋果
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150) 
                                            .or(a -> "green".equals(a.getColor()));
請註意,and和or方法是按照在表達式鏈中的位置,從左向右確定優 先級的。因此,a.or(b).and(c)可以看作(a || b) && c。

7.3 函數複合

andThen方法會返回一個函數,它先對輸入應用一個給定函數,再對輸出應用另一個函數。 比如,
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);
數學上會寫作g(f(x))或(g o f)(x)
這將返回4

compose方法,先把給定的函數用作compose的參數裡面給的那個函 數,然後再把函數本身用於結果。
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1);
數學上會寫作f(g(x))或(f o g)(x) 
這將返回3

8. 小結

以下是你應從本章中學到的關鍵概念。

  • Lambda表達式可以理解為一種匿名函數:它沒有名稱,但有參數列表、函數主體、返回類型,可能還有一個可以拋出的異常的列表。
  • Lambda表達式讓你可以簡潔地傳遞代碼。
  • 函數式介面就是僅僅聲明瞭一個抽象方法的介面。
  • 只有在接受函數式介面的地方纔可以使用Lambda表達式。
  • Lambda表達式允許你直接內聯,為函數式介面的抽象方法提供實現,並且將整個表達式作為函數式介面的一個實例。
  • Java 8自帶一些常用的函數式介面,放在java.util.function包里,包括Predicate、Function<T,R>、Supplier、Consumer和BinaryOperator,如表3-2所述。
  • 為了避免裝箱操作,對Predicate和Function<T, R>等通用函數式介面的原始類型特化:IntPredicate、IntToLongFunction等。
  • 環繞執行模式(即在方法所必需的代碼中間,你需要執行點兒什麼操作,比如資源分配 和清理)可以配合Lambda提高靈活性和可重用性。
  • Lambda表達式所需要代表的類型稱為目標類型。
  • 方法引用讓你重覆使用現有的方法實現並直接傳遞它們。
  • Comparator、Predicate和Function等函數式介面都有幾個可以用來結合Lambda表達式的預設方法。

資源獲取

  • 公眾號回覆 : Java8 即可獲取《Java 8 in Action》中英文版!

Tips

  • 歡迎收藏和轉發,感謝你的支持!(๑•̀ㅂ•́)و✧
  • 歡迎關註我的公眾號:莊裡程式猿,讀書筆記教程資源第一時間獲得!

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 會話技術: 會話是什麼? 瀏覽器和伺服器交互,瀏覽器打開網頁訪問伺服器,會話開始,正常交互. 瀏覽器關閉,會話結束. 會話能幹什麼? 會話可以共用數據. Cookie和session將數據保存在不同的位置 進行數據共用 Cookie入門案例 1.創建一個cookie對象 a. Cookie cook ...
  • 1.iter補充 2.ntp_client和ntp_server 3.time複習 4.udp的客戶端與服務端通信 5.解決粘包 # from socket import * # ip_port=('127.0.0.1',8080) # back_log=5 # buffer_size=1024 # ...
  • 1. course 1.進程創建的兩種方式 1. 開啟進程的第一種方式: 2. 開啟進程的第二種方式: 3. 簡單應用 2.獲取進程pid 3.驗證進程之間的空間隔離 4. join 5.進程的其他參數 6.守護進程 7.僵屍進程孤兒進程 基於 環境( ) 主進程需要等待子進程結束之後,主進程才結束 ...
  • 服務鏈路跟蹤 背景 微服務以微出名,在實際的開發過程中,涉及到成百上千個服務,網路請求引起服務之間的調用極其複雜。 當請求不可用或者變慢時,需要及時排查出故障服務點成為了微服務維護的一大難關。 服務鏈路跟蹤技術應運而生。 ZipKin Zipkin 是一個開放源代碼分散式的跟蹤系統,由Twitter ...
  • Python 入門之常用運算符 算數運算符 比較運算符 賦值運算符 邏輯運算符 成員運算符 位運算符 身份運算符 Python運算符優先順序 ...
  • HTTP不能保持連接,可使用會話保存用戶信息。 常用的會話技術有2種:Cookie、Session。 Cookie 1、原理 當用戶第一次訪問某個網站時,伺服器設置Cookie,存儲用戶信息,放在響應頭欄位中,隨HTTP響應傳給瀏覽器,瀏覽器把Cookie存儲到本地電腦上。 當用戶再次訪問該網站時 ...
  • 摘要: 在 的版本變遷過程中,註解發生了很多的變化,然而代理的設計也發生了微妙的變化,從 的`ProxyFactoryBean Spring2.x Aspectj`註解,最後到了現在廣為熟知的自動代理。 說明: 代理的相關配置類 實現了 ,封裝了對 和`Advisor`的操作 該類及其子類主要是利用 ...
  • django搭建BBS 表單創建&註冊 0824自我總結 文件結構 app 介面 migrations _\_inint\_\_.py admin.py apps.py bbsform.py models.py tests.py views.py avatar BBS \_\_inint\_\_.p ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...