java 表達式有很多種,聲明一個class是一個表達式,定義一個變量是一個表達式,寫一個=指派邏輯是一個表達式……
lambda表達式是這樣一個表達式:
lambdaparameters -> lambdabody
在lambdaparameters傳遞參數,在lambdabody中編寫邏輯。lambda表達式生成的結果就是一個函數式接口(上文提到過的)。lambdabody中的邏輯内容(各種表達式)不會在定義時執行,在實際函數式接口調用時才會執行。
舉幾個官方的例子看看:
可以通過上面的例子看到,lambda的參數聲明主要包含兩大類,一類是聲明類型的,一類是不聲明類型的(依賴推斷的)。其中聲明類型的參數,與定義一個方法時聲明參數是一樣的。
幾個注意的點:
_不能作為lambda參數。
int...與int[]是一緻的。
當參數是推斷類型時,注意推斷類型的類型轉換錯誤,類型是依據上下文變化的。
來個推斷的例子:
body部分的形式同一個方法的描述基本一緻,或者是一個表達式,或者是一個block代碼。整體了解lambda的參數和body,可以對應上一節的function接口來看:()的參數部分,對應function的第一個泛型參數;{}或者類似x+1這樣的表達式作為body,對應function的第二個泛型參數。空參數對應supplier,而空return對應consumer。
不同于匿名内部類的形式,lambda表達式的body共享上下文類的this變量。另一個注意點是lambda表達式的body裡包含的外部變量,變量需要是final的或者effectively final。
effectively final的定義如下:
如果是有初始值的變量(指派過一次),需要滿足:
沒有聲明final
從未出現在指派語句的左值部分
從未作為一個變量被++或--之類的遞增遞減形式操作過
如果是沒有初始值的變量,需要滿足:
在實際指派前,絕對未指派或者未絕對指派
方法、構造函數、lambda和異常的參數,會被認為是effectively final
這裡又引入兩個概念:絕對指派和絕對未指派。
絕對指派:變量在複雜邏輯中的每個執行路徑中都保證指派語句存在。
絕對未指派:變量在複雜邏輯中的每個執行路徑中都保證沒有指派語句存在。
看個例子:(絕對指派,需要注釋掉n=6)
不滿足絕對指派:
絕對未指派:
不滿足絕對未指派:
body部分也表達出了一部分相容性,即當body部分是表達式語句時,如果語句允許獨立執行,那麼該表達式等價于body部分是void傳回值的。即如下的例子,list.add是個傳回boolean的方法,因為可以獨立執行,那麼下面的例子都是ok的:
方法引用表達式是另一類執行函數式接口的模式,在java 8之前是沒有能力表達一個函數方法的,在java 8引入函數式接口後,每個lambda表達式都代表了一個函數,可以指向性的将lambda表達式指派給一個function類的接口。另一個重要的方法就是直接使用函數方法引用。
方法引用是通過[對象名]::[方法名]這種模式來引用的,其中::兩個冒号的操作符非常重要。具體的場景針對類、對象執行個體、數組、泛型等均有不同的支援,下面的例子看看各種方法引用的表達方式:
其中需要注意的是,數組的new方法引用等價于一個有入參的function,因為new一個數組是需要指定size的。
無論lambda表達式還是方法引用表達式,所指向的都是一個方法或者是函數。而它們指向的内容能指派的也一定是函數式接口。這兩種指向也是實用場景各異,方法引用需要使用在已有方法上(顯而易見),而lambda表達式是一種快速行内聲明一個方法且指向一個函數式接口的方法。兩者互動配合,基本可以覆寫各種函數式接口使用的場景。