Function接口的使用

Java8 添加了一個新的特性Function,顧名思義這一定是一個函數式的操作。我們知道Java8的最大特性就是函數式接口。所有標註了@FunctionalInterface註解的接口都是函數式接口,具體來說,所有標註了該註解的接口都將能用在lambda表達式上。

標註了@FunctionalInterface的接口有很多,但此篇我們主要講Function,瞭解了Function其他的操作也就很容易理解了。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    /**
     * @return a composed function that first applies the {@code before}
     * function and then applies this function
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    /**
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
}

爲了方便地閱讀源碼,我們需要了解一些泛型的知識,如果你對泛型已經很熟悉了,那你可以跳過這段 。

泛型是JDK1.5引入的特性,通過泛型編程可以使編寫的代碼被很多不同的類型所共享,這可以很好的提高代碼的重用性。因爲本篇重點不是介紹泛型,所以我們只關注上述Function源碼需要用到的泛型含義。

1. 泛型類

泛型類使用來表示該類爲泛型類,其內部成員變量和函數的返回值都可以爲泛型 ,Function源碼的標識爲<T,R>,也就是兩個泛型參數,此處不再贅述,具體泛型類可以看網上的文章。

2. 泛型方法和通配符

在方法修飾符的後面加一個表明該方法爲泛型方法,如Function 的源碼裏的compose方法的。通配符也很好理解,還是compose的例子,我們可以看到compose的參數爲一個Function類型,其中Functin的參數指定了其第一個參數必須是V的父類,第二個參數必須繼承T,也就是T的子類。

源碼解析

1.apply

講完了上面這些就可以開始研究源碼了。

首先我們已經知道了Function是一個泛型類,其中定義了兩個泛型參數T和R,在Function中,T代表輸入參數,R代表返回的結果。也許你很好奇,爲什麼跟別的java源碼不一樣,Function 的源碼中並沒有具體的邏輯呢?
在這裏插入圖片描述
其實這很容易理解,Function 就是一個函數,其作用類似於數學中函數的定義 ,(x,y)跟<T,R>的作用幾乎一致。

所以Function中沒有具體的操作,具體的操作需要我們去爲它指定,因此apply具體返回的結果取決於傳入的lambda表達式。

 R apply(T t);

舉個例子:

public void test(){
    Function<Integer,Integer> test=i->i+1;
    test.apply(5);
}
/** print:6*/

我們用lambda表達式定義了一個行爲使得i自增1,我們使用參數5執行apply,最後返回6。這跟我們以前看待Java的眼光已經不同了,在函數式編程之前我們定義一組操作首先想到的是定義一個方法,然後指定傳入參數,返回我們需要的結果。函數式編程的思想是先不去考慮具體的行爲,而是先去考慮參數,具體的方法我們可以後續再設置。

再舉個例子:

public void test(){
    Function<Integer,Integer> test1=i->i+1;
    Function<Integer,Integer> test2=i->i*i;
    System.out.println(calculate(test1,5));
    System.out.println(calculate(test2,5));
}
public static Integer calculate(Function<Integer,Integer> test,Integer number){
    return test.apply(number);
}
/** print:6*/
/** print:25*/

我們通過傳入不同的Function,實現了在同一個方法中實現不同的操作。在實際開發中這樣可以大大減少很多重複的代碼,比如我在實際項目中有個新增用戶的功能,但是用戶分爲VIP和普通用戶,且有兩種不同的新增邏輯。那麼此時我們就可以先寫兩種不同的邏輯。除此之外,這樣還讓邏輯與數據分離開來,我們可以實現邏輯的複用。

當然實際開發中的邏輯可能很複雜,比如兩個方法F1,F2都需要兩個個邏輯AB,但是F1需要A->B,F2方法需要B->A。這樣的我們用剛纔的方法也可以實現,源碼如下:

public void test(){
    Function<Integer,Integer> A=i->i+1;
    Function<Integer,Integer> B=i->i*i;
    System.out.println("F1:"+B.apply(A.apply(5)));
    System.out.println("F2:"+A.apply(B.apply(5)));
}
/** F1:36 */
/** F2:26 */

也很簡單呢,但是這還不夠複雜,假如我們F1,F2需要四個邏輯ABCD,那我們還這樣寫就會變得很麻煩了。

2.compose和andThen

compose和andThen可以解決我們的問題。先看compose的源碼

  default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

compose接收一個Function參數,返回時先用傳入的邏輯執行apply,然後使用當前Function的apply。

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

andThen跟compose正相反,先執行當前的邏輯,再執行傳入的邏輯。

這樣說可能不夠直觀,我可以換個說法給你看看

compose等價於B.apply(A.apply(5)),而andThen等價於A.apply(B.apply(5))。

public void test(){
    Function<Integer,Integer> A=i->i+1;
    Function<Integer,Integer> B=i->i*i;
    System.out.println("F1:"+B.apply(A.apply(5)));
    System.out.println("F1:"+B.compose(A).apply(5));
    System.out.println("F2:"+A.apply(B.apply(5)));
    System.out.println("F2:"+B.andThen(A).apply(5));
}
/** F1:36 */
/** F1:36 */
/** F2:26 */
/** F2:26 */

我們可以看到上述兩個方法的返回值都是一個Function,這樣我們就可以使用建造者模式的操作來使用。

B.compose(A).cpmpose(A).andThen(A).apply(5);

這個操作很簡單,你可以自己試試。

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