Java8新特性(1):Lambda表達式
Lambda表達式可以理解為一種匿名函數:沒有名稱,但有參數列表、函數主體、返回類型。它是行為參數化的一種實現,行為參數化是指將不同的行為作為參數傳遞給方法,方法的所具備的能力取決於它接收的行為參數。使用Lambda表達式使我們不必為這些行為去編寫一堆固定的實現類就能應對不斷變化的需求,在1.8之前,可以使用匿名內部類的方式達到相同的效果,只是相對於Lambda表達式來說,匿名內部類的方式會顯得啰嗦。
函數式接口
Lambda表達式的使用依賴於函數式接口,只有在接受函數式接口的地方才可以使用Lambda表達式。函數式接口是指只聲明了一個抽象方法的接口,可以有多個靜態方法、默認方法,如下所示:
@FunctionalInterface
public interface Calculation {
int calculate(int a, int b);
}
@FunctionalInterface註解表示被標註的接口將被設計成一個函數式接口,不是必須的,它主要是在接口違背函數式接口原則時會出現編譯錯誤。比如修改Calculation接口,再添加一個抽象方法就會出現Multiple non-overriding abstract methods found in interface com.cf.demo.lambda.Calculation編譯錯誤:
//編譯錯誤:Multiple non-overriding abstract methods found in interface com.cf.demo.lambda.Calculation
@FunctionalInterface
public interface Calculation {
int calculate(int a, int b);
int calculate2(int a, int b);
}
注意:Object類的方法是特例,即使接口聲明了多個Object類的方法,也不會被算入「只聲明了一個抽象方法」的計數中。如下Calculation接口是正確的函數式接口:
@FunctionalInterface
public interface Calculation {
int calculate(int a, int b);
boolean equals(Object obj);
}
Java8提供了一些常用的函數式接口,位於java.util.function包下,並且為了避免裝箱操作,還提供了和基本類型對應的接口,我們在實際使用時,可以優先使用這些內置的函數式接口。當然在某些情況我們也需要使用自定義的函數式接口,如需要在Lambda表達式中拋異常時,這種情況就需要自定義一個函數式接口,並聲明異常。
Lambda表達式語法
Lambda表達式由參數列表、箭頭(Lambda操作符)、Lambda主體三個部分組成。Lambda表達式的參數列表要和函數式接口的參數列表相對應,Lambda主體的返回值也要和函數式接口的返回類型相對應。現在有如下doArithmetic方法,接收兩個整型參數以及一個Calculation,doArithmetic方法的行為是由傳遞的Calculation來決定的,我們可以調用該方法傳遞不同的Calculation來完成不同的計算:
public static int doArithmetic(int a, int b, Calculation calculation){
return calculation.calculate(a, b);
}
現在要計算兩個數的乘積,用內部類的方式:
int result = doArithmetic(3, 2, new Calculation() {
@Override
public int calculate(int a, int b) {
return a * b;
}
});
System.out.println(result);//6
用Lambda表達式的方式要更簡潔:
int result = doArithmetic(3, 2, (int a, int b) -> a * b);
System.out.println(result);//6
(int a, int b)是Lambda表達式的參數列表部分,只有一個參數的時候可以省略小括號,這裡有多個參數,所以要保留小括號。參數類型可以省略,因為Java編譯器能通過上下文推斷出數據類型,無需顯示的聲明:
int result = doArithmetic(3, 2, (a, b) -> a * b);
System.out.println(result);//6
Lambda主體只有一個語句時,可以省略{}和return,(int a, int b) -> a * b)就是省略之後的寫法,我們也可以使用完整的寫法:
int result = doArithmetic(3, 2, (a, b) -> {
return a * b;
});
System.out.println(result);//6
當需要在Lambda表達式中使用『外部局部變量』時,這個『外部局部變量』默認是final的,『外部局部變量』這裡是指非Lambda表達式內部定義的局部變量。修改doArithmetic方法,添加一個『外部局部變量』,為乘積賦個初始值,以下代碼是編譯不通過的:
int initialValue = 1;
int result = doArithmetic(3, 2, (a, b) -> a * b + initialValue);
initialValue = 2;//Variable used in lambda expression should be final or effectively final
System.out.println(result);
方法引用
方法引用可以對『某種特殊情況』下的Lambda表達式進行簡化,『某種特殊情況』是指Lambda表達式要做的事情別的方法實現了,那我們就可以直接使用這個方法,然後像Lambda表達式一樣傳遞即可。方法引用的語法為目標引用放在分隔符::前,方法的名稱放在後面,目標引用可以是類名也可以是對象名。通過以下三個例子來介紹方法引用的三種使用方法,新增Arithmetic類,Arithmetic類包含一個靜態方法和一個實例方法:
public class Arithmetic {
public static int multiply(int a, int b){
return a * b;
}
public int add(int a, int b){
return a + b;
}
}
1.指向靜態方法的方法引用
int multiplyResult = doArithmetic(3, 2, Arithmetic::multiply);
System.out.println(multiplyResult);//6
2.指向現有對象的實例方法的方法引用
Arithmetic arithmetic = new Arithmetic();
int addResult = doArithmetic(3, 2, arithmetic::add);
System.out.println(addResult);//5
3.指向任意類型實例方法的方法引用,這種情況有個特點,就是在引用一個對象的方法,而這個對象本身是Lambda的一個參數。比如現在需要實現比較兩個數的大小,首先修改calculate方法參數類型為包裝類型Integer:
@FunctionalInterface
public interface Calculation {
int calculate(Integer a, Integer b);
}
比較a和b的大小可以這樣寫:
int result = doArithmetic(3, 2, Integer::compareTo);//Integer::compareTo等於a.compareTo(b)
System.out.println(result);//1
構造函數引用
對於一個現有構造函數,可以使用它的名稱和new來創建一個它的引用: ClassName::new。再使用構造函數引用時,需要調用的構造器參數列表要和函數式接口的抽象方法的參數要一致。舉個例子,現在添加了兩個生成String對象的方法:
public static String generateString(Supplier<String> supplier) {
return supplier.get();
}
public static String generateString(String value, Function<String, String> function) {
return function.apply(value);
}
分別使用構造函數引用:
String result = generateString(String::new);//調用String()構造方法
System.out.println(result);
result = generateString("hello Lambda", String::new);//調用String(String original)構造方法
System.out.println(result);