Java8新特性(lambda表示式)
1.1 lambda表示式簡介
理解lambda表示式的Java實現,有兩個結構十分關鍵:第一個是lambda表示式自身,第二個是函式式介面。
lambda表示式本質上就是一個匿名(即未命名)方法。但是,這個方法不是獨立執行的,而是用於實現由函式式介面定義的另一個方法。因此,lambda表示式會產生一個匿名類。lambda表示式也常被稱為閉包。
函式式介面是僅包含一個抽象方法的介面。這個方法指明瞭介面的目標用途。因此,函式式介面通常表示單個動作。例如:標準介面Runnable是一個函式式介面,因為它只定義了一個方法run(),run()定義了Runnable的動作。此外,介面式函式定義了lambda表示式的目標型別。特別注意:lambda表示式只能用於其目標型別已被指定的上下文中。
1.1.1 lambda表示式的基礎知識
lambda表示式在Java語言中引入了一個新的語法元素和操作符。這個操作符是-> ,操作符被稱為lambda操作符或者是箭頭操作符。它將lambda表示式分成兩個部分,左側指定了lambda表示式需要的所有引數(如果不需要引數,則使用空的引數列表“()”),右側指定了lambda體,即lambda表示式要執行的動作。
Java定義了兩種lambda體:一種包含單獨一個表示式,另一種包含一個程式碼塊。
看一個簡單的lambda表示式的例子,它的計算結果是一個常量值,如下所示:
() -> 123.45
這個lambda表示式沒有引數,所以引數列表為空,它返回常量值123.45。因此,這個表示式的作用類似於下面的方法;
double myMath(){
return 123.45;
}
lambda表示式定義的方法沒有名稱
下面在看一個lambda表示式:
() -> Math.random() * 100
這個lambda表示式使用Math.random()獲得一個隨機數,將其乘以100,然後返回結果。這個lambda表示式也不需要引數。
當lambda表示式需要引數時,需要在操作符左側的引數列表中加以指定,下面是一個簡單的例子:
(n) -> (n % 2) == 0
如果引數n的值是偶數,這個lambda表示式會返回true。儘管可以顯式指定引數的型別,例如本例中的n,但是通常不需要這麼做,因為很多時候,引數的型別是可以從上下文中推斷出來。與命名方法一樣,lambda表示式可以指定需要用到的任意數量的引數。
1.1.2 函式式介面
函式式介面是僅指定了一個抽象方法的介面。從JDK8開始,可以為介面宣告的方法指定預設行為(預設的方法實現),即所謂的預設方法,只有當沒有指定預設實現時,介面的方法才是抽象方法。因為沒有指定預設實現的介面方法隱式的是抽象方法,所以沒有必要使用abstract修飾符。
下面是函式式介面的一個例子:
public interface MyNumber {
double getValue();
}
在本例中,getValue()方法隱式的是抽象方法,並且是MyNumber定義的唯一方法,所以MyNumber是一個函式式介面,其功能由getValue()定義。
lambda表示式不是獨立執行的,而是構成了一個函式式介面定義的抽象方法的實現,該函式式介面定義了它的目標型別,只有在定義了lambda表示式的目標型別的上下文中,才能使用該表示式。當把一個lambda表示式賦給一個函式式介面引用時,就建立了這樣的上下文。
下面通過一個例子來說明如何在引數上下文中使用lambda表示式。首先,宣告對函式式介面MyNumber的一個引用
MyNumber myNum;
接下來,將一個lambda表示式賦給該介面引用:
myNum = () -> 123.45;
當目標型別上下文中出現lambda表示式時,會自動建立實現了函式式介面的一個類的例項,函式式介面宣告的抽象方法的行為由lambda表示式定義。當通過目標呼叫該方法時,就會執行lambda表示式。因此,lambda表示式提供了一種將程式碼片段轉換為物件的方法。
在前面的例子中,lambda表示式成了getValue()方法的實現,所以,下面的程式碼將顯示123.45:
System.out.println(myNum.getValue());
//結果如下:
//123.45
因為賦給myNum的lambda表示式返回值為123.45,所以呼叫getValue()方法返回的值也是123.45
為了在目標型別上下文中使用lambda表示式,抽象方法的型別和lambda表示式的型別必須相容。例如:如果抽象方法指定了兩個int型別的引數,那麼lambda表示式也必須指定兩個引數,其型別要麼被顯式的指定為int型別,要麼在上下文中可以隱式的推斷為int型別。總的來說,lambda表示式的引數型別和數量必須與方法的引數相容;返回型別必須相容;並且lambda表示式可能丟擲的異常必須能被方法接受。
package lambda;
interface NumericTest{
boolean test(int n);
}
public class LambdaDemo2 {
public static void main(String[] args) {
NumericTest isEven = (n) -> (n % 2) == 0;
if (isEven.test(10)){
System.out.println("10 是偶數");
}
if (!isEven.test(9)){
System.out.println("9不是偶數");
}
NumericTest isNonNeg = (n) -> n >= 0;
if (isNonNeg.test(1)){
System.out.println("1是正數");
}
if (!isNonNeg.test(-1)){
System.out.println("-1不是正數");
}
}
}
//結果:
//10 是偶數
//9不是偶數
//1是正數
//-1不是正數
這個程式演示了關於lambda表示式的一個重要的地方:函式式介面引用可以用來執行任何與其相容的lambda表示式。注意,程式中定義了兩個不同的lambda表示式,它們都是與函式式介面NumericTest的test()方法相容,所以都可以通過NumericTest引用執行。
當lambda表示式中有多個引數時,如下所示:
(n,d) -> (n % d) == 0;
兩個引數n和d才引數列表中指定,並且用逗號隔開。每當需要一個以上的引數時,就在lambda表示式操作符左側,使用一個帶括號的引數列表指定引數,引數之間使用逗號隔開。
對於lambda表示式中的多個引數,有一點十分重要:如果需要顯式宣告一個引數型別,那麼必須為所有的引數宣告型別。例如下面的程式碼是合法的:
(int n,int d) -> (n % d) == 0;
這是不合法的:
(int n, d) -> (n % d) == 0;
1.2 塊lambda表示式
塊lambda表示式是操作符的右側的程式碼不是一個簡單的表示式,而是由一個程式碼塊組成,其中包含多條語句。這種型別的lambda體被稱為塊體,具有塊體的lambda表示式被稱為塊lambda。
塊lambda擴充套件了lambda表示式內部可以處理的操作型別,因為它允許lambda體包含多條語句。例如,在塊lambda中可以宣告變數、使用迴圈、指定if和switch語句、建立巢狀程式碼塊等。建立塊lambda只需要使用花括號包圍lambda體,就像建立其他語句塊一樣。
在塊lambda中重要一點是:在塊lambda中必須顯式使用return語句來返回值。因為塊lambda體代表的不是單獨一個表示式。
下面這個例子使用塊lambda來計算並返回一個int型別值的階乘:
package lambda;
interface NumuricFunc{
int func(int n);
}
public class BlockLambdaDemo {
public static void main(String[] args) {
NumuricFunc factorial = (n) -> {
int result = 1;
for (int i = 1;i <= n;i++){
result *= i;
}
return result;
};
System.out.println(factorial.func(3));
System.out.println(factorial.func(6));
}
}
//結果如下:
//6
//720
在程式中,塊lambda聲明瞭變數result,使用了for迴圈,有一條return語句。在lambda表示式中出現return語句時,只是從lambda體返回,而不會導致包圍lambda提的方法返回。
