lambda表達式和stream流

lambda表達式

基本格式

(Object o1, Object o2) -> {方法體}

o1、o2:方法參數

->:特定的箭頭符號

{...}:方法體內容,實際的代碼

注:當方法體只有一行時,花括號可省略。

函數式接口

函數式接口就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。

@FunctionalInterface

java提供上述註解標記一個接口爲函數式接口,該註解爲非強制添加註解,僅僅作爲一個定義的條件約束,也就是說如果一個接口加上了該註解,那麼該接口必須滿足函數式接口的定義。

注:重寫/定義父類的方法,不影響上述的判斷規則,接口只要符合上述規則即可。

 

核心函數式接口

消費接口

Consumer<T>,接口源碼如下:

 

顧名思義,該接口的accept方法實現要求傳入一個泛型參數,在方法體中做出某些操作,無返回值(void),就像現實生活中的某些消費操作一樣,把錢交給對方後,對方提供某些服務。該接口大多數用於集合遍歷操作中(可以通過項目代碼看出),對元素進行消費操作。

常見變種接口:

生產接口

Supplier<T>,接口源碼如下:

 

該接口get方法實現無入參,返回值爲指定泛型類型,該接口可以用來生產元素。

常見變種接口:

謂詞接口

Predicate<T>,接口部分源碼如下:

 

該接口test方法實現要求傳入一個泛型參數t,返回值爲boolean,因此該接口常常用來做元素的過濾和篩選。

常見變種接口:

函數接口

Function<T, R>,接口部分源碼如下:

 

該接口apply方法實現要求傳入一個泛型參數t,返回值爲另一個泛型類型R,該接口主要用來進行元素的映射和轉換。

常見變種接口:

類型檢查

Lambda的類型是從使用Lambda的上下文推斷出來的。上下文(比如,接受它傳遞的方法的參數,或接受它的值的局部變量)中Lambda表達式需要的類型稱爲目標類型。類型檢查描述示例如下圖所示(部分圖例摘自《Java8實戰》)。

 

類型推斷

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

 

方法引用

方法引用可以被看作僅僅調用特定方法的Lambda的一種快捷寫法。它的基本思想是,如果一個Lambda代表的只是“直接調用這個方法”,那最好還是用名稱來調用它,而不是去描述如何調用它。方法引用的一些例子如下圖所示。

 

方法引用構建方法主要有以下三類。

靜態方法的調用

當調用類的靜態方法時,可以簡寫爲類名::方法名(不帶括號)的格式。

額外補充說明如下:

無參靜態方法調用

可以直接使用方法引用。

具體示例代碼如下:

Test類靜態方法:

 

Test類main方法代碼:

 

帶參的靜態方法引用

調用的靜態方法中的參數個數,必須要和lambda參數個數保持一致,且不能包含其他外部參數,否則無法使用方法引用。

具體示例代碼如下:

Test類中帶參的靜態方法如下:

 

Test2類中帶參的靜態方法如下:

 

Test類main方法代碼:

 

Test類靜態方法,3個參數:

 

Test類main方法代碼:

 

lambda表達式中參數對象的實例方法調用

當調用lambda表達式中參數對象的實例方法時,可以簡寫爲類名::調用方法名(不帶括號)的格式。

額外補充說明如下:

無參實例方法調用

lambda參數僅有一個時,可以將其轉換爲方法引用;

若lambda參數多個,則無法轉換爲方法引用。

具體示例代碼如下:

Test類,無參實例方法如下:

 

TestInterface接口如下,test方法僅有一個參數:

 

Test類main方法代碼:

 

TestInterface1接口如下,test方法包含多個參數:

 

Test類main方法代碼:

 

帶參實例方法調用

lambda參數僅有一個時,無論實例方法參數類型和個數,均不能使用方法引用;

lambda參數爲多個時,必須滿足方法體中調用方法的對象爲lambda參數的第一個參數,並且剩餘的參數,類型和個數要和調用的方法的類型和個數匹配,且剩餘參數均要在調用方法的其他參數列表裏,個數只能出現一次,不允許出現其他外部參數。

具體示例代碼如下:

Test類中,帶參實例方法如下:

 

Test類main方法代碼:

 

Test類中,帶參實例方法如下:

 

Test類main方法代碼:

 

 

將TestInterface接口的test方法修改爲如下格式,包含3個參數:

 

Test類main方法代碼:

 

外部實例對象的實例方法調用

當調用其他實例對象的實例方法時,可以簡寫爲實例對象名::調用方法名(不帶括號)的格式。

額外補充說明如下:

無參實例方法調用

不存在lambda參數時,可以使用方法引用;

存在lambda參數時,不可以使用方法引用。

具體示例代碼如下:

TestInterface3接口如下:

 

TestInterface4接口如下:

 

Test類,main方法代碼:

 

 

帶參實例方法調用

不存在lambda參數時,無法使用方法引用;

存在lambda參數,且lambda參數和調用的實例方法參數類型、個數、順序保持一致,則可以使用方法引用。

具體示例代碼如下:

TestInterface接口代碼如下:

 

TestInterface3接口代碼如下:

 

Test類,實例方法如下:

 

 

Test類,main方法代碼:

 

 

Stream流

基本概念

流是Java8新增的特性,簡單來說,流能讓你更加簡單地對數據的集合進行排序、過濾、映射等處理。使用流有以下幾點好處:聲明性(代碼更簡潔、易讀)、可複合(更靈活)、可並行(充分發揮性能)。

這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等;元素流在管道中經過中間操作的處理,最後由最終操作得到前面處理的結果。

可以將stream流操作想象成工廠裏的流水線操作,原料經過一系列的流水線加工,最終包裝成產品。

 

中間/終端操作

java.util.stream.Stream中的Stream接口定義了許多操作,它們可以分爲兩大類,以下列代碼舉例說明:

List<String> names = menu.stream()

            .filter(d -> d.getCalories() > 300)(過濾)

            .map(Dish::getName)(映射)

            .limit(3)(取前3條數據)

            .collect(toList());(聚合)

其中,filter、map和limit可以連成一條流水線;collect觸發流水線執行並關閉它。

可以連接起來的流操作稱爲中間操作。諸如 filter 或 sorted 等中間操作會返回另一個流。這讓多個操作可以連接起來形成一個查詢。重要的是,除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理(延遲操作)。這是因爲中間操作一般都可以合併起來,在終端操作時一次性全部處理。

關閉流的操作稱爲終端操作。終端操作會從流的流水線生成結果,該結果是任何不是流的值,比如List、Integer甚至是void(如進行遍歷操作)。

使用流的步驟

第一步:從一個數據源(如集合)調用指定方法,獲取流對象;

第二步:對於流對象,可以有一個或多箇中間操作鏈,形成一條流的流水線操作;

第三步:執行某個終端操作,流水線此時纔開始執行,並生成結果。

流的創建

由值創建流:Stream.of()靜態方法,傳入對象/對象數組;

由數組創建流:Arrays.stream()靜態方法,傳入數組;

由文件創建流:Files.lines()靜態方法,傳入文件路徑(Path);

由函數創建流:Stream. iterate()靜態方法(迭代),Stream.generate()靜態方法(生成)。

注:使用函數創建流時,一定要調用limit方法限制流元素的個數,否則流會無限生成元素,導致程序運行異常。

示例代碼:

 

收集器

Collector<T, A, R>

接口部分源碼如下

 

泛型說明:

T:進行歸約操作的元素類型;

A:歸約操作的中間類型;

R:歸約操作最終返回的類型。

 

方法說明:

supplier():流元素容器提供;

accumulator():流元素歸約操作;

combiner():將兩個部分結果合併到一個結果中。該方法適用於parallelStream()並行流;

finisher():將中間類型結果轉換爲最終類型;

characteristics():收集器的特性。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章