在本文中,我們將通過示例了解Java lambda表達式,以及l(fā)ambda表達式與函數接口、泛型函數接口和流API的使用。
lambda表達式是在Java 8中首次引入的。其主要目的是提高語言的表達能力。
但是,在學習lambda之前,我們首先需要了解功能接口。
如果Java接口僅包含一個抽象方法,則將其稱為功能接口。僅這一種方法指定了接口的預期用途。
例如,包java.lang中的Runnable接口;是一個功能接口,因為它只組成一個方法,即run()。
import java.lang.FunctionalInterface; @FunctionalInterface public interface MyInterface{ //單一抽象方法 double getValue(); }
在上面的示例中,接口MyInterface只有一個抽象方法getValue()。因此,它是一個功能接口。
在這里,我們使用了注解@FunctionalInterface。該注解會強制Java編譯器指示該接口是功能接口。因此,不允許有多個抽象方法。但是,它不是強制性的。
在Java 7中,功能接口被視為單一抽象方法(SAM)類型。在Java 7中,SAM類型通常是通過匿名類實現的。
public class FunctionInterfaceTest { public static void main(String[] args) { //匿名類 new Thread(new Runnable() { @Override public void run() { System.out.println("我剛剛實現了Runnable功能接口。"); } }).start(); } }
輸出:
我剛剛實現了Runnable功能接口。
在這里,我們可以將匿名類傳遞給方法。這有助于用Java 7編寫代碼更少的程序。但是,語法仍然很困難,需要大量的額外代碼行。
Java 8進一步擴展了SAM的功能。由于我們知道功能接口只有一個方法,因此在將其作為參數傳遞時,無需定義該方法的名稱。Lambda表達式使我們能夠做到這一點。
Lambda表達式本質上是一個匿名或未命名的方法。lambda表達式不能單獨執(zhí)行。相反,它用于實現功能接口定義的方法。
這是我們如何在Java中定義lambda表達式。
(parameter list) -> lambda body
使用的新運算符(->)被稱為箭頭運算符或lambda運算符。讓我們探索一些實例,
假設我們有一個這樣的方法:
double getPiValue() { return 3.1415; }
我們可以使用lambda表達式編寫此方法,如下所示:
() -> 3.1415
在此,該方法沒有任何參數。因此,運算符的左側包括一個空參數。右側是lambda主體,用于指定lambda表達式的操作。在這種情況下,它將返回值3.1415。
在Java中,lambda主體有兩種類型。
1.單個表達式主體
() -> System.out.println("Lambdas are great");
這種類型的lambda主體稱為表達式主體。
2.由代碼塊組成的主體。
() -> { double pi = 3.1415; return pi; };
這種類型的lambda體稱為塊體。塊主體允許lambda主體包含多個語句。這些語句包含在括號內,您必須在括號后添加分號。
注意:對于塊體,您應該始終有一個return語句。但是,單個表達式主體不需要return語句。
讓我們編寫一個Java程序,該程序使用lambda表達式返回Pi的值。
如前所述,lambda表達式不是單獨執(zhí)行的。相反,它形成了由功能接口定義的抽象方法的實現。
因此,我們需要首先定義一個功能接口。
import java.lang.FunctionalInterface; //這是功能接口 @FunctionalInterface interface MyInterface{ // 抽象方法 double getPiValue(); } public class Main { public static void main( String[] args ) { //聲明對MyInterface的引用 MyInterface ref; // lambda 表達式 ref = () -> 3.1415; System.out.println("Pi = " + ref.getPiValue()); } }
輸出:
Pi = 3.1415
在以上示例中,
我們創(chuàng)建了一個名為MyInterface的功能接口。它包含一個名為getPiValue()的抽象方法
在Main類內部,我們聲明了對MyInterface的引用。請注意,我們可以聲明接口的引用,但不能實例化接口。那是因為,
//它將拋出一個錯誤 MyInterface ref = new myInterface(); // 這是有效的 MyInterface ref;
然后,我們?yōu)橐梅峙淞艘粋€lambda表達式。
ref = () -> 3.1415;
最后,我們使用reference接口調用方法getPiValue()。
System.out.println("Pi = " + ref.getPiValue());
到現在為止,我們已經創(chuàng)建了不帶任何參數的lambda表達式。但是,類似于方法,lambda表達式也可以具有參數。例如,
(n) -> (n%2)==0
在此,括號內的變量n是傳遞給lambda表達式的參數。Lambda主體接受參數并檢查其是偶數還是奇數。
@FunctionalInterface interface MyInterface { //抽象方法 String reverse(String n); } public class Main { public static void main( String[] args ) { //聲明對MyInterface的引用 //將lambda表達式分配給引用 MyInterface ref = (str) -> { String result = ""; for (int i = str.length()-1; i >= 0 ; i--){ result += str.charAt(i); } return result; }; //調用接口的方法 System.out.println("Lambda reversed = " + ref.reverse("Lambda")); } }
輸出:
Lambda reversed = adbmaL
到目前為止,我們已經使用了僅接受一種類型的值的功能接口。例如,
@FunctionalInterface interface MyInterface { String reverseString(String n); }
上面的功能接口僅接受String并返回String。但是,我們可以使功能接口通用,以便接受任何數據類型。如果不熟悉泛型,請訪問Java泛型。
// GenericInterface.java @FunctionalInterface interface GenericInterface<T> { // 泛型方法 T func(T t); } // GenericLambda.java public class Main { public static void main( String[] args ) { //聲明對GenericInterface的引用 // GenericInterface對String數據進行操作 //為其分配一個lambda表達式 GenericInterface<String> reverse = (str) -> { String result = ""; for (int i = str.length()-1; i >= 0 ; i--) result += str.charAt(i); return result; }; System.out.println("Lambda reversed = " + reverse.func("Lambda")); //聲明對GenericInterface的另一個引用 // GenericInterface對整數數據進行操作 //為其分配一個lambda表達式 GenericInterface<Integer> factorial = (n) -> { int result = 1; for (int i = 1; i <= n; i++) result = i * result; return result; }; System.out.println("5的階乘 = " + factorial.func(5)); } }
輸出:
Lambda reversed = adbmaL 5的階乘 = 120
在上面的示例中,我們創(chuàng)建了一個名為GenericInterface的泛型功能接口。它包含一個名為func()的泛型方法。
在類內部:
GenericInterface<String> reverse - 創(chuàng)建對該接口的引用。 現在,該接口可以處理String類型的數據。
GenericInterface<Integer> factorial -創(chuàng)建對該接口的引用。 在這種情況下,該接口對Integer類型的數據進行操作。
新的java.util.stream包已添加到JDK8中,它允許java開發(fā)人員執(zhí)行搜索、過濾、映射、減少等操作,或者操作列表等集合。
例如,我們有一個數據流(在我們的示例中是一個字符串列表),其中每個字符串都是國家名稱和國家/地區(qū)的組合。 現在,我們可以處理此數據流,并且僅從Nepal檢索位置。
為此,我們可以結合使用Stream API和Lambda表達式在流中執(zhí)行批量操作。
import java.util.ArrayList; import java.util.List; public class StreamMain { //使用ArrayList創(chuàng)建一個列表對象 static List<String> places = new ArrayList<>(); //準備我們的數據 public static List getPlaces(){ //將地點和國家添加到列表中 places.add("Nepal, Kathmandu"); places.add("Nepal, Pokhara"); places.add("India, Delhi"); places.add("USA, New York"); places.add("Africa, Nigeria"); return places; } public static void main( String[] args ) { List<String> myPlaces = getPlaces(); System.out.println("Places from Nepal:"); myPlaces.stream() .filter((p) -> p.startsWith("Nepal")) .map((p) -> p.toUpperCase()) .sorted() .forEach((p) -> System.out.println(p)); } }
輸出:
Places from Nepal: NEPAL, KATHMANDU NEPAL, POKHARA
在上面的示例中,請注意以下語句:
myPlaces.stream() .filter((p) -> p.startsWith("Nepal")) .map((p) -> p.toUpperCase()) .sorted() .forEach((p) -> System.out.println(p));
在這里,我們使用的是Stream API的filter(),map()和forEach()之類的方法。 這些方法可以將lambda表達式作為輸入。
我們還可以根據上面學習的語法定義自己的表達式。如上例所示,這使我們可以大大減少代碼行。