函數式接口
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方法。
捐贈
若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。