五分鐘學習 Java 8 行為引數化
1、概覽
Java8的改進比歷史上任何一次改變都比較深遠。Java不斷改進也是程式語言生態變化的使然——諸如大資料需要在多核上面執行,而Java此前是不支援這種操作的。
在Java8之前,如果想要利用多個計算機的核心,你要使用執行緒,並且要處理複雜的同步邏輯。但是在Java8中,你可以很容易地使用流讓自己的程式碼在多個核心上面執行。
此外,它還借鑑了其他語言和開源庫的內容,比如Scala、Guava等。我們總結一下Java8的主要幾個特徵或者改進:
- 函數語言程式設計和Lambda表示式;
- 流(Stream)程式設計;
- 時間API的改進;
- 預設方法
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; }
從上面的示例程式碼,我們可以總結出一些結論:
- 所謂的函式介面就是指只包含一個非預設方法的介面,可以用
@FunctionalInterface
註解標明指定的介面是函式介面; - 如果Lambda中的
->
後面的是語句,並且當該語句只有一行的時候,我們可以將花括號去掉; - 想要將Lambda表示式賦值給一個物件的時候,如果這個物件不是
函式介面
,那麼IDEA會給提示; - 還要注意函式式介面是不允許丟擲受檢異常的。
下面我們總結一些常見的方法引用的示例:
上面程式碼中的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);
}
上面就是這三個介面的定義。它們的應用場景的不同就體現再返回的引數上面:
- 第一個用來判斷的,大致用來實現過濾的效果;
- 第二是沒有返回型別,只能用來對傳入的引數進行處理;
- 第三個是用來對映的,也就是說,當你想要實現的行為的引數和返回是不同的型別的時候可以用它(當然,如果是相同型別的話也是可以的)。
因為對於數值型別,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中提供了一些有用的函式式介面,這些介面又可以使用複合方法實現更加強大的功能。
我是 WngShhng. 如果您喜歡我的文章,可以在以下平臺關注我: