Java8實(shí)戰(zhàn)之行為參數(shù)化與Lambda
前言
現(xiàn)在Java的迭代速度比以前快了很多,然而,本渣渣最近才開始學(xué)習(xí)Java8,相比于之前的版本,Java8中引入了許多新的特性,其中最主要的是Lambda、Stream、Optional<T>、新的時間日期API,接下來將分成幾個小節(jié),分別學(xué)習(xí)這些內(nèi)容,并且將學(xué)習(xí)筆記整理出來,參考書籍為《Java8實(shí)戰(zhàn)》。
本小節(jié)主要學(xué)習(xí)行為參數(shù)化以及Lambda
行為參數(shù)化
在Java8之前,Java中的方法或者說函數(shù)是二等公民,也就是說,方法或者函數(shù)是無法作為參數(shù)進(jìn)行傳遞的,隨著編程思想的逐步發(fā)展,函數(shù)式編程的思想越來越受到重視,其優(yōu)勢也逐漸凸顯出來,Java8引入了對應(yīng)的解決方法,提供了一種類似的方式來處理,使得可以將函數(shù)作為參數(shù)進(jìn)行傳遞,從而實(shí)現(xiàn)了類似函數(shù)式編程的功能,亦即Lamda。
所謂的形式參數(shù)化,就是將行為,通常表現(xiàn)為函數(shù)或者方法做為參數(shù)進(jìn)行傳遞,這種編程方式的好處在于,可以將變化的部分抽取出來,形成函數(shù),然后根據(jù)情況傳遞實(shí)現(xiàn)的內(nèi)容,使得整體具有更高的靈活性。
下面的舉例內(nèi)容大致來自于書中的例子,通過該例子,可以看出行為參數(shù)化的優(yōu)勢
class Apple {
private int weight;
private String color;
// 省略set、get方法
}
對于上面的Apple類,如果我們想根據(jù)不同的情況進(jìn)行篩選,實(shí)現(xiàn)方式有多種,一種是根據(jù)需要篩選的條件提供對應(yīng)的篩選方法,如下所示
public List<Apple> filterAppleByWeight(List<Apple> apples) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (apple.getWeight() > 30) {
result.add(apple);
}
}
return result;
}
public List<Apple> filterAppleByColor(List<Apple> apples) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (apple.getColor().equals("green")) {
result.add(apple);
}
}
return result;
}
上面的方式看起來沒有什么問題,而且非常直觀,當(dāng)需要增加新的過濾條件時,只需要復(fù)制一份,然后更改過濾條件就行,但實(shí)際上這種方式問題是比較大的
- 存在著明顯的代碼冗余,代碼冗余就意味著如果需要修改,需要注意的地方會比較多
- 不利于變化,比如說現(xiàn)在需要過濾
weight > 30 && color == "green"的,那么就需要為其再編寫對應(yīng)的處理函數(shù),而改變的僅僅是其中的一行代碼。尤其是當(dāng)屬性比較多的時候,各種情況組合起來,需要考慮的情況非常多,代碼也會變得非常復(fù)雜。
由于上面的方式比較難以應(yīng)對各種情況,所以,更好地方式是采用類似策略模式的形式,將其中可能經(jīng)常變化的部分抽取出來放在接口里面,在運(yùn)行時通過傳入不同的實(shí)現(xiàn)類來處理
// 過濾器接口
interface AppleFilter {
boolean filter(Apple apple);
}
// 過濾器實(shí)現(xiàn)
class GreenAppleFilter implements AppleFilter {
@Override
public boolean filter(Apple apple) {
return "green".equals(apple.getColor());
}
}
// 過濾蘋果操作
public List<Apple> static filterApple(List<Apple> apples, AppleFilter appleFilter) {
List<Apple> result = new ArrayList<>();
for (Apple apple : apples) {
if (appleFilter.filter(apple)) {
result.add(apple);
}
}
return result;
}
public void operation() {
AppleFilter appleFilter = new GreenAppleFilter();
filterApple(apples, appleFilter);
}
通過上面的方式,可以在需要的時候創(chuàng)建對應(yīng)的過濾器實(shí)現(xiàn)類,然后傳遞給過濾操作函數(shù)即可,通過這種方式,可以避免前面出現(xiàn)的修改問題。
然而,上面的這種方式其實(shí)并不優(yōu)雅,也不方便,由于我們需要根據(jù)不同的情況,編寫不同的實(shí)現(xiàn)類,這雖然方便解耦,但卻增大了不必要的工作量。
通過匿名內(nèi)部類的形式,我們就可以在不編寫對應(yīng)實(shí)現(xiàn)類的情況下,直接將對應(yīng)的實(shí)現(xiàn)傳遞給對應(yīng)的方法,所以,上面的代碼可以直接簡化為
public void operation() {
// 匿名內(nèi)部類實(shí)現(xiàn)
filterApple(apples, new AppleFilter() {
@Override
public boolean filter(Apple apple) {
return "green".equals(apple.getColor());
}
});
}
這樣就可以不用編寫實(shí)現(xiàn)類,然后再來實(shí)現(xiàn)對應(yīng)的操作了,這種方式已經(jīng)是非常優(yōu)雅了,不過,仍然不是最優(yōu)雅的方式,因?yàn)槲覀冞€需要new一個對象,然后實(shí)現(xiàn)其方法。
上面的實(shí)現(xiàn)方式是在Java8之前的方式,在Java8之后,通過Lambda,我們可以將上面的方式更佳地簡化,最終代碼如下所示
public void operation() {
filterApple(apples, apple -> "green".equals(apple.getColor()));
}
可以看到,Lambda表達(dá)式以及其簡潔的方式來實(shí)現(xiàn)我們的需求,并且滿足了對改動等的要求,所以,下面我們就詳細(xì)地學(xué)習(xí)Lambda表達(dá)式。
Lambda表達(dá)式
Lambda表達(dá)式是在Java8之后引入的一種新的代碼編寫方式,通過Lambda表達(dá)式,可以在某種程度上使得代碼變得更加簡潔,尤其是在某個函數(shù)只需要使用一次的時候,無需再為他們設(shè)計(jì)專門的類以及方法。
Lambda表達(dá)式可以簡單地理解匿名函數(shù),所以Lambda表達(dá)式具有函數(shù)除了名字之外的的所有特性
- 可以有一個或者多個參數(shù)
- 可以具有返回值也可以沒有
- 具有方法體,也就是Lambda所要執(zhí)行的內(nèi)容
將Lambda表達(dá)式理解為匿名函數(shù)之后,關(guān)于Lambda表達(dá)式的寫法就比較好理解了,Lambda具有兩種最基本的寫法
// 表達(dá)式
() -> EXP
// 語句
() -> {STATEMENT1; STATEMENT2; ...}
其中的->用于分隔參數(shù)以及方法體,是必須具備的,如果只有一個參數(shù),()可以不用,如apple -> {} 和(apple) -> {}是等價的,而且,在Lambda表達(dá)式參數(shù)中,可以不用寫上類型,原因在于lambda可以根據(jù)使用的場景進(jìn)行推導(dǎo),后面展開。
注意上面的兩種形式,其中的表達(dá)式?jīng)]有{},并且不需要;只能有一個表達(dá)式(表達(dá)式的返回值就是Lambda的值),而對于語句來講可以有多個表達(dá)式,每個表達(dá)式后面需要有;,最后一個表達(dá)式作為Lambda表達(dá)式的返回值。
函數(shù)接口
上面我們看到了Lambda的寫法,接下來我們來看下Lambda表達(dá)式的應(yīng)用場景,并不是說每個Lambda表達(dá)式都可以作為參數(shù)傳遞到任意的方法中,Lambda表達(dá)式需要符合某種規(guī)則(參數(shù),返回值),而這些規(guī)則,就來自于函數(shù)接口。
函數(shù)接口是一種特別的接口,這種接口是專門為Lambda表達(dá)式創(chuàng)建的,其特性表現(xiàn)為
- 只能有一個抽象方法(可以有多個默認(rèn)方法,關(guān)于默認(rèn)方法,將在后面的小節(jié)學(xué)習(xí)到)
- 可以使用
@FunctionalInterface,進(jìn)行修飾
比如上面的AppleFilter就是一個函數(shù)接口,而下面的接口就不是了,因?yàn)橛胁恢挂粋€抽象方法
interface AppleFilter {
boolean filter(Apple apple);
boolean match(Apple apple);
}
在傳遞Lambda表達(dá)式的時候,在方法的參數(shù)中,需要提供一個函數(shù)接口,才能將Lambda表達(dá)式傳遞給該方法,并且該Lambda表達(dá)式必須與函數(shù)接口中的抽象方法簽名一致,可以簡單地理解為該Lambda表達(dá)式就是該接口的一個實(shí)現(xiàn)。
public void operation() {
filterApple(apples, apple -> "green".equals(apple.getColor()));
// 其中的Lambda表達(dá)式 apple -> "green".equals(apple.getColor());
// 跟接口中的抽象方法簽名 boolean filter(Apple apple); 是一致的
// 一個參數(shù),一個Boolean類型的返回值
}
同時我們也提到了Lambda表達(dá)式參數(shù)可以不用寫類型,可以根據(jù)情況進(jìn)行推導(dǎo),其原因也在于此,當(dāng)我們把一個Lambda表達(dá)式傳遞給一個方法的時候,由于該方法中的函數(shù)接口中的匿名方法簽名已經(jīng)是確定的了,比如上面的boolean filter(Apple apple),所以,當(dāng)使用Lambda表達(dá)式 apple -> "green".equals(apple.getColor())的時候,編譯器可以根據(jù)方法簽名中的方法類型來推導(dǎo)出Lambda表達(dá)式中的參數(shù)簽名,進(jìn)而對后面的內(nèi)容進(jìn)行驗(yàn)證,參數(shù)個數(shù)以及返回值也是如此,而且可以驗(yàn)證整個Lambda表示式是否是符合對應(yīng)抽象方法簽名的。
這里有個地方需要注意,同一個Lambda表示式可以用在不同的函數(shù)接口的,只需要符合其方法簽名即可,同時,一個Lambda表達(dá)式可以直接賦值給其對應(yīng)的函數(shù)接口對象
interface AppleFilter {
boolean filter(Apple apple);
}
interface Predict {
boolean test(Apple apple);
}
// 上面的Lambda表達(dá)式可以用于這兩個接口
// 如果結(jié)合泛型的話,則同一個Lambda表達(dá)式可以使用的場景就更多了
由于如果每次需要,都要設(shè)計(jì)對應(yīng)的函數(shù)接口,也是一件非常麻煩的事情,所以,在JDK中提供了基本上涵蓋了我們所需要的函數(shù)接口了,可以根據(jù)需要使用即可,其中常用的幾個如下所示
public interface Consumer<T> {
void accept(T t);
}
public interface Function<T, R> {
R apply(T t);
}
public interface Supplier<T> {
T get();
}
public interface Predicate<T> {
boolean test(T t);
}
// 更多的函數(shù)接口可以參考 package java.util.function
總結(jié)
本小節(jié)主要學(xué)習(xí)了行為參數(shù)化的作用以及意義,Lambda表達(dá)式的寫法,函數(shù)接口的限制以及作用,常見的幾個函數(shù)接口。關(guān)于Lambda表達(dá)式的寫法,多練習(xí)幾次就熟悉了,在Java8中引入的其他幾個新功能,基本都是構(gòu)建在Lambda表示式之上的,所以,目前來說,對Lambda表示式只需要有個概念性的理解即可,后面經(jīng)過Stream的練習(xí)之后,就會熟悉Lambda表達(dá)式了。