Java8新特性(1):Lambda表示式

布禾發表於2020-04-24

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);

相關文章