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():收集器的特性。