五分鐘學習 Java 8 行為引數化

WngShhng發表於2018-05-22

1、概覽

Java8的改進比歷史上任何一次改變都比較深遠。Java不斷改進也是程式語言生態變化的使然——諸如大資料需要在多核上面執行,而Java此前是不支援這種操作的。

在Java8之前,如果想要利用多個計算機的核心,你要使用執行緒,並且要處理複雜的同步邏輯。但是在Java8中,你可以很容易地使用流讓自己的程式碼在多個核心上面執行。

此外,它還借鑑了其他語言和開源庫的內容,比如Scala、Guava等。我們總結一下Java8的主要幾個特徵或者改進:

  1. 函數語言程式設計和Lambda表示式;
  2. 流(Stream)程式設計;
  3. 時間API的改進;
  4. 預設方法

2、行為引數化

“行為”就是指方法,“行為引數化”就是指將方法作為引數傳入,說白了就是指策略模式。而Java8只是使用了Lambda表示式簡化了匿名類的程式碼,使匿名類看起來更加簡潔。在這塊內容上,你所需要掌握的東西並不多。

2.1 Lambda表示式基本語法

(parameters) -> expression     // 表示式
(parameters) -> {statements;}  // 語句,語句尾帶分號且要用花括號括起來
複製程式碼

上面是Lambda的基本語法,第一行中是Lambda中使用表示式的情況,第二行中式Lambda中使用語句的情況。

下面是一些使用Lambda表示式的示例:

public static void main(String...args) {
    // 建立物件
    ICreateObject createObject = Employee::new;
    IExpression expression = employees -> employees.get(0);
    // 可以進一步簡化為 IExpression expression2 = List::isEmpty;
    IExpression expression2 = employees -> employees.isEmpty(); 
    IConsumeObject consumeObject = employee -> System.out.println(employee.name);
    IAdd add = (a, b) -> a + b;
    IAdd add1 = Java8LambdaExample::cal;
    // 會報出不是Function介面異常
    //        Object object = Employee::new;
}
複製程式碼

從上面的示例程式碼,我們可以總結出一些結論:

  1. 所謂的函式介面就是指只包含一個非預設方法的介面,可以用@FunctionalInterface註解標明指定的介面是函式介面;
  2. 如果Lambda中的->後面的是語句,並且當該語句只有一行的時候,我們可以將花括號去掉;
  3. 想要將Lambda表示式賦值給一個物件的時候,如果這個物件不是函式介面,那麼IDEA會給提示;
  4. 還要注意函式式介面是不允許丟擲受檢異常的。

下面我們總結一些常見的方法引用的示例:

上面程式碼中的Employee::new就是所謂的方法引用,下面是常見的方法引用的例子:

編號 Lambda 等效的方法引用
1 (Employee e)->e.getName() Employee::getName
2 (String s) -> System.out.println(s) System.out::println
3 (str, i) -> str.substring(i) String::substring

所以,我們總結下來的三種方法引用的情形:

編號 Lambda 等效的方法引用
1 (引數) -> 類名.靜態方法(引數) 類名::靜態方法
2 (引數1, 其他引數) -> 引數1.例項方法(其他引數) 類名::例項方法
3 (引數) -> 表示式.例項方法(引數) 表示式::例項方法

2.2 Java API 中的函式式介面

Java8的API中為我們提供了幾個函式式介面,這些介面有必要了解一下。因為自從Java8開始介面可以定義預設方法了,所以這些介面裡面又提供了一些有意思的預設方法。這可能對我們程式設計比較有幫助。

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

public interface Consumer<T> {
    void accept(T t);
}

public interface Function<T, R> {
    R apply(T t);
}
複製程式碼

上面就是這三個介面的定義。它們的應用場景的不同就體現再返回的引數上面:

  1. 第一個用來判斷的,大致用來實現過濾的效果;
  2. 第二是沒有返回型別,只能用來對傳入的引數進行處理;
  3. 第三個是用來對映的,也就是說,當你想要實現的行為的引數和返回是不同的型別的時候可以用它(當然,如果是相同型別的話也是可以的)。

因為對於數值型別,Java需要做額外的裝箱和拆箱的操作,這是需要成本的。所以,對於上面的三個介面(其他的介面也是),Java8中提供了不需要裝箱的版本,也就是從泛型變成了數值型別而已。以IntPredicate為例:

public interface IntPredicate {
    boolean test(int value);
}
複製程式碼

2.3 複合Lambda表示式

Java8中提供的一些介面還是可以複合操作的。使用複合操作可以實現更復雜的邏輯。這些複合操作是以預設方法的形式定義的,每個函式式介面略有不同。所以,我們這裡只列舉出部分用於複合的方法。在實際的開發過程中,你可以直接進入到指定的函式式介面中檢視這些方法的定義。

2.3.1 比較器Comparator

假設有一個資料列表employees,其中的物件是Employee,它有getName()和getAge()兩個方法。

employees.sort(Comparator.comparing(Employee::getName));
employees.sort(Comparator.comparing(Employee::getName).reversed().thenComparing(Employee::getAge));
複製程式碼

上面的兩行程式碼中,第一行實現對employees按照getName()的結果進行排序。第二行程式碼對employees,先按照getName()的結果進行排序,然後將返回的結果逆序,再按照getAge()的結果進行排序。

2.3.2 謂詞複合 negate()、or()和and()

Predicate<Employee> employeePredicate = (employee -> employee.getAge() > 13)
employeePredicate.negate()
employeePredicate.and(employee -> employee.getAge() <= 15).or(employee -> "LiHua".equals(employee.getName()))
複製程式碼

這裡首先定義了employeePredicate,它可以用來過濾“年齡大於13的僱員”。對其呼叫了negate()方法將返回一個Predicate,可以用來過濾“年紀小於等於13的僱員”。最後的複合操作則表示“年齡大於13並且小於15的僱員或者名字為LiHua的僱員”。

注意,這裡的and和or操作的順序是從左向右的,也就是a.or(b).and(c)將被看作(a || b) and c

2.3.3 函式Function複合

Function有andThen和compose兩個預設方法,它們都會返回一個Function例項。

Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h1 = f.andThen(g); // h1(x) = g(f(x)) = (x + 1) * 2
Function<Integer, Integer> h2 = f.compose(g); // h2(x) = f(g(x)) = (x * 2) + 1
System.out.println(h1.apply(1));
System.out.println(h2.apply(1));
複製程式碼

上面是Function的複合操作的示例,其實它的效果就相當於數學中的複合函式。不過,應當注意一下兩個方法的實際的複合效果是不同的。

總結

以上就是Java8改進的第一部分,總結一下:行為引數化其實就是策略模式,使用Lambda可以簡化函式介面的形式;Java API中提供了一些有用的函式式介面,這些介面又可以使用複合方法實現更加強大的功能。

相關文章