1. 程式人生 >實用技巧 >其它 >Java8新特性(lambda表示式)

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提的方法返回。