【java基礎(四十)】lambda表達式(二)

函數式接口

Java中已經有很多封裝代碼塊的接口,如ActionListener或Comparator。lambda表達式與這些接口是兼容的。

對於只有一個抽象方法的接口,需要這種接口對象時,就可以提供一個lambda表達式。這種接口稱爲函數式接口(functional interface)。

爲了展示如何轉換爲函數式接口,下面考慮Arrays.sort方法。它的第二個參數需要一個Comparator實例,Comparator就是隻有一個方法的接口,所以可以提供一個lambda表達式:

Arrays.sort(words, (first, second) -> first.length() - second.length());

在底層,Arrays.sort方法會接收實現了Comparator<String>的某個類的對象。在這個對象上調用compare方法會執行這個lambda表達式的體。這些對象和類的管理完全取決於具體實現,與使用傳統的內聯類相比,這樣可以要高效很多。最好把lambda表達式看作是一個函數,而不是一個對象,另外要接受lambda表達式可以傳遞到函數式接口。

lambda表達式可以轉換爲接口,這一點讓lambda表達式很有吸引力。具體的語法很簡短:

Timer t = new Timer(1000, event -> {
	System.out.println("At the tone, the time is " + new Date());
	Toolkit.getDefaultTookit().beep();
});

與使用實現了ActionListener接口的類相比,這個代碼可讀性要好很多。

實際上,在Java中,對lambda表達式所能做的也只是能轉換函數式接口。在其他支持函數字面量的程序設計語言中,可以聲明函數類型(如(String, String)-> int)、聲明這些類型的變量,還可以使用變量保存函數表達式,不過,Java設計者還是決定保持我們熟悉的接口概念,沒有爲Java語言增加函數類型。

Java API在java.util.function包中定義了很多非常通用的函數式接口。其中一個接口BiFunction<T, U, R>描述了參數類型爲T和U而且返回類型爲R的函數。可以把我們的字符串比較lambda表達式保存在這個類型的變量中:

BiFunction<String, String, Integer> comp = (first, second) -> first.length() - second.length();

不過,這對於排序並沒有幫助。沒有哪個Arrays.sort方法想要接收一個BiFunction。如果你之前用過某種函數式程序設計語言,可能會發現這很奇怪。不過,對於Java程序猿而言,這非常自然。類似Comparator的接口往往有一個特定的用途,而不只是提供一個有指定參數和返回類型的方法。Java SE8沿襲了這種思路。想要用lambda表達式做某些處理,還是要謹記表達式的用途,爲它建立一個特定的函數式接口。

java.uti.function包中有一個尤其有用的接口Predicate:

public interface Predicate<T> {
	boolean test(T t);
}

ArrayList類有一個removeIf方法,它的參數就是一個Predicate。這個接口專門用來傳遞lambda表達式。例如,下面的語句將從一個數組列表刪除所有null值:

list.removeIf(e -> e == null);

方法引用

有時,可能已經有現成的方法可以完成你想要傳遞到其他代碼的某個動作。假如,假設你希望只要出現一個定時器事件就打印這個事件對象。當然,你可以這樣調用:

Timer t = new Timer(1000, event -> System.out.println(event));

但是,如果直接把println方法傳遞到Timer構造器就更好了。具體做法如下:

Timer t = new Timer(1000, System.out::println);

表達式System.out::println是一個方法引用(method reference),它等價於lambda表達式x -> System.out.println(x);

假設你想對字符串排序,而不考慮字母的大小寫。可以傳遞以下方法表達式:

Arrays.sort(strings, String::compareToIgnoreCase);

從這些例子可以看出,要用::操作符分隔方法名與對象或類名。主要有3中情況:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

在前2中情況下,方法引用等價於提供方法參數的lambda表達式。前面已經提到,System.out::println等價於x -> System.out.println(x)。類似的,Math::pow等價於(x, y) -> Math.pow(x, y);

對於第3中情況,第1個參數會成爲方法的目標。例如:String::compareToIgnoreCase等同於(x, y) -> x.compareToIgnoreCase(y);

可以在方法引用中使用this參數。例如,this::equals等同於x -> this.equals(x)。使用super也是合法的:

super::instanceMethod

使用super作爲目標,會調用給定方法的超類版本。如:

class Greeter {
	public void greet() {
		System.out.println("hello, world");
	}
}

class TimedGreeter extends Greeter {
	public void greet() {
		Timer t = new Timer(1000, super::greet);
		t.start();
	}
}

TimedGreeter.greet方法開始執行時,會構造一個Timer,它會在每次定時器滴答時執行super::greet方法。這個方法會調用超類的greet方法。

捐贈

若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章