Java8 lambda表達式10個示例

Java8 lambda表達式10個示例

例1、用lambda表達式實現Runnable

我開始使用Java 8時,首先做的就是使用lambda表達式替換匿名類,而實現Runnable接口是匿名類的最好示例。看一下Java 8之前的runnable實現方法,需要4行代碼,而使用lambda表達式只需要一行代碼。我們在這裏做了什麼呢?那就是用() -> {}代碼塊替代了整個匿名類

複製代碼

// Java 8之前:
new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("Before Java8, too much code for too little to do");
    }
}).start();

//Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();

複製代碼

輸出:

too much code, for too little to do
Lambda expression rocks !!

這個例子向我們展示了Java 8 lambda表達式的語法。你可以使用lambda寫出如下代碼:

(params) -> expression
(params) -> statement
(params) -> { statements }

例如,如果你的方法不對參數進行修改、重寫,只是在控制檯打印點東西的話,那麼可以這樣寫:

() -> System.out.println("Hello Lambda Expressions");

如果你的方法接收兩個參數,那麼可以寫成如下這樣:

(int even, int odd) -> even + odd

順便提一句,通常都會把lambda表達式內部變量的名字起得短一些。這樣能使代碼更簡短,放在同一行。所以,在上述代碼中,變量名選用a、b或者x、y會比even、odd要好。

例2、使用Java 8 lambda表達式進行事件處理

如果你用過Swing API編程,你就會記得怎樣寫事件監聽代碼。這又是一箇舊版本簡單匿名類的經典用例,但現在可以不這樣了。你可以用lambda表達式寫出更好的事件監聽代碼,如下所示:

複製代碼

// Java 8之前:
JButton show =  new JButton("Show");
show.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
    System.out.println("Event handling without lambda expression is boring");
    }
});

// Java 8方式:
show.addActionListener((e) -> {
    System.out.println("Light, Camera, Action !! Lambda expressions Rocks");
});

複製代碼

Java開發者經常使用匿名類的另一個地方是爲 Collections.sort() 定製 Comparator。在Java 8中,你可以用更可讀的lambda表達式換掉醜陋的匿名類。我把這個留做練習,應該不難,可以按照我在使用lambda表達式實現 Runnable 和 ActionListener 的過程中的套路來做。

例3、使用lambda表達式對列表進行迭代

如果你使過幾年Java,你就知道針對集合類,最常見的操作就是進行迭代,並將業務邏輯應用於各個元素,例如處理訂單、交易和事件的列表。由於Java是命令式語言,Java 8之前的所有循環代碼都是順序的,即可以對其元素進行並行化處理。如果你想做並行過濾,就需要自己寫代碼,這並不是那麼容易。通過引入lambda表達式和默認方法,將做什麼和怎麼做的問題分開了,這意味着Java集合現在知道怎樣做迭代,並可以在API層面對集合元素進行並行處理。下面的例子裏,我將介紹如何在使用lambda或不使用lambda表達式的情況下迭代列表。你可以看到列表現在有了一個 forEach()  方法,它可以迭代所有對象,並將你的lambda代碼應用在其中。

複製代碼

// Java 8之前:
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
for (String feature : features) {
    System.out.println(feature);
}

// Java 8之後:
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
features.forEach(n -> System.out.println(n));
 
// 使用Java 8的方法引用更方便,方法引用由::雙冒號操作符標示,
// 看起來像C++的作用域解析運算符
features.forEach(System.out::println);

複製代碼

輸出:

Lambdas
Default Method
Stream API
Date and Time API

列表循環的最後一個例子展示瞭如何在Java 8中使用方法引用(method reference)。你可以看到C++裏面的雙冒號、範圍解析操作符現在在Java 8中用來表示方法引用。

例4、使用lambda表達式和函數式接口Predicate

除了在語言層面支持函數式編程風格,Java 8也添加了一個包,叫做 java.util.function。它包含了很多類,用來支持Java的函數式編程。其中一個便是Predicate,使用 java.util.function.Predicate 函數式接口以及lambda表達式,可以向API方法添加邏輯,用更少的代碼支持更多的動態行爲。下面是Java 8 Predicate 的例子,展示了過濾集合數據的多種常用方法。Predicate接口非常適用於做過濾。

複製代碼

public static void main(args[]){
    List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");
 
    System.out.println("Languages which starts with J :");
    filter(languages, (str)->str.startsWith("J"));
 
    System.out.println("Languages which ends with a ");
    filter(languages, (str)->str.endsWith("a"));
 
    System.out.println("Print all languages :");
    filter(languages, (str)->true);
 
    System.out.println("Print no language : ");
    filter(languages, (str)->false);
 
    System.out.println("Print language whose length greater than 4:");
    filter(languages, (str)->str.length() > 4);
}
 
public static void filter(List names, Predicate condition) {
    for(String name: names)  {
        if(condition.test(name)) {
            System.out.println(name + " ");
        }
    }
}

複製代碼

輸出:

複製代碼

Languages which starts with J :
Java
Languages which ends with a
Java
Scala
Print all languages :
Java
Scala
C++
Haskell
Lisp
Print no language :
Print language whose length greater than 4:
Scala
Haskell

複製代碼

// 更好的辦法
public static void filter(List names, Predicate condition) {
    names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
        System.out.println(name + " ");
    });
}

可以看到,Stream API的過濾方法也接受一個Predicate,這意味着可以將我們定製的 filter() 方法替換成寫在裏面的內聯代碼,這就是lambda表達式的魔力。另外,Predicate接口也允許進行多重條件的測試,下個例子將要講到。

例5、如何在lambda表達式中加入Predicate

上個例子說到,java.util.function.Predicate 允許將兩個或更多的 Predicate 合成一個。它提供類似於邏輯操作符AND和OR的方法,名字叫做and()、or()和xor(),用於將傳入 filter() 方法的條件合併起來。例如,要得到所有以J開始,長度爲四個字母的語言,可以定義兩個獨立的 Predicate 示例分別表示每一個條件,然後用 Predicate.and() 方法將它們合併起來,如下所示:

複製代碼

// 甚至可以用and()、or()和xor()邏輯函數來合併Predicate,
// 例如要找到所有以J開始,長度爲四個字母的名字,你可以合併兩個Predicate並傳入
Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
names.stream()
    .filter(startsWithJ.and(fourLetterLong))
    .forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : " + n));

複製代碼

類似地,也可以使用 or() 和 xor() 方法。本例着重介紹瞭如下要點:可按需要將 Predicate 作爲單獨條件然後將其合併起來使用。簡而言之,你可以以傳統Java命令方式使用 Predicate 接口,也可以充分利用lambda表達式達到事半功倍的效果。

例6、Java 8中使用lambda表達式的Map和Reduce示例

本例介紹最廣爲人知的函數式編程概念map。它允許你將對象進行轉換。例如在本例中,我們將 costBeforeTax 列表的每個元素轉換成爲稅後的值。我們將 x -> x*x lambda表達式傳到 map() 方法,後者將其應用到流中的每一個元素。然後用 forEach() 將列表元素打印出來。使用流API的收集器類,可以得到所有含稅的開銷。有 toList() 這樣的方法將 map 或任何其他操作的結果合併起來。由於收集器在流上做終端操作,因此之後便不能重用流了。你甚至可以用流API的 reduce() 方法將所有數字合成一個,下一個例子將會講到。

複製代碼

// 不使用lambda表達式爲每個訂單加上12%的稅
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
for (Integer cost : costBeforeTax) {
    double price = cost + .12*cost;
    System.out.println(price);
}
 
// 使用lambda表達式
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
costBeforeTax.stream().map((cost) -> cost + .12*cost).forEach(System.out::println);

複製代碼

輸出:

複製代碼

112.0
224.0
336.0
448.0
560.0
112.0
224.0
336.0
448.0
560.0

複製代碼

例6.2、Java 8中使用lambda表達式的Map和Reduce示例

在上個例子中,可以看到map將集合類(例如列表)元素進行轉換的。還有一個 reduce() 函數可以將所有值合併成一個。Map和Reduce操作是函數式編程的核心操作,因爲其功能,reduce 又被稱爲摺疊操作。另外,reduce 並不是一個新的操作,你有可能已經在使用它。SQL中類似 sum()、avg() 或者 count() 的聚集函數,實際上就是 reduce 操作,因爲它們接收多個值並返回一個值。流API定義的 reduceh() 函數可以接受lambda表達式,並對所有值進行合併。IntStream這樣的類有類似 average()、count()、sum() 的內建方法來做 reduce 操作,也有mapToLong()、mapToDouble() 方法來做轉換。這並不會限制你,你可以用內建方法,也可以自己定義。在這個Java 8的Map Reduce示例裏,我們首先對所有價格應用 12% 的VAT,然後用 reduce() 方法計算總和。

複製代碼

// 爲每個訂單加上12%的稅
// 老方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double total = 0;
for (Integer cost : costBeforeTax) {
    double price = cost + .12*cost;
    total = total + price;
}
System.out.println("Total : " + total);
 
// 新方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
System.out.println("Total : " + bill);

複製代碼

輸出:

Total : 1680.0
Total : 1680.0

例7、通過過濾創建一個String列表

過濾是Java開發者在大規模集合上的一個常用操作,而現在使用lambda表達式和流API過濾大規模數據集合是驚人的簡單。流提供了一個 filter() 方法,接受一個 Predicate 對象,即可以傳入一個lambda表達式作爲過濾邏輯。下面的例子是用lambda表達式過濾Java集合,將幫助理解。

// 創建一個字符串列表,每個字符串長度大於2
List<String> filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList());
System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);

輸出:

Original List : [abc, , bcd, , defg, jk], filtered list : [abc, bcd, defg]

另外,關於 filter() 方法有個常見誤解。在現實生活中,做過濾的時候,通常會丟棄部分,但使用filter()方法則是獲得一個新的列表,且其每個元素符合過濾原則。

例8、對列表的每個元素應用函數

我們通常需要對列表的每個元素使用某個函數,例如逐一乘以某個數、除以某個數或者做其它操作。這些操作都很適合用 map() 方法,可以將轉換邏輯以lambda表達式的形式放在 map() 方法裏,就可以對集合的各個元素進行轉換了,如下所示。

// 將字符串換成大寫並用逗號鏈接起來
List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
System.out.println(G7Countries);

輸出:

USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA

例9、複製不同的值,創建一個子列表

本例展示瞭如何利用流的 distinct() 方法來對集合進行去重。

// 用所有不同的數字創建一個正方形列表
List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List<Integer> distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
System.out.printf("Original List : %s,  Square Without duplicates : %s %n", numbers, distinct);

輸出:

Original List : [9, 10, 3, 4, 7, 3, 4],  Square Without duplicates : [81, 100, 9, 16, 49]

例10、計算集合元素的最大值、最小值、總和以及平均值

IntStream、LongStream 和 DoubleStream 等流的類中,有個非常有用的方法叫做 summaryStatistics() 。可以返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistic s,描述流中元素的各種摘要數據。在本例中,我們用這個方法來計算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法來獲得列表的所有元素的總和及平均值。

複製代碼

//獲取數字的個數、最小值、最大值、總和以及平均值
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());

複製代碼

輸出:

Highest prime number in List : 29
Lowest prime number in List : 2
Sum of all prime numbers : 129
Average of all prime numbers : 12.9

Lambda表達式 vs 匿名類

既然lambda表達式即將正式取代Java代碼中的匿名內部類,那麼有必要對二者做一個比較分析。一個關鍵的不同點就是關鍵字 this。匿名類的 this 關鍵字指向匿名類,而lambda表達式的 this 關鍵字指向包圍lambda表達式的類。另一個不同點是二者的編譯方式。Java編譯器將lambda表達式編譯成類的私有方法。使用了Java 7的 invokedynamic 字節碼指令來動態綁定這個方法。

Java 8 Lambda表達式要點

10個Java lambda表達式、流API示例

到目前爲止我們看到了Java 8的10個lambda表達式,這對於新手來說是個合適的任務量,你可能需要親自運行示例程序以便掌握。試着修改要求創建自己的例子,達到快速學習的目的。我還想建議大家使用Netbeans IDE來練習lambda表達式,它對Java 8支持良好。當把代碼轉換成函數式的時候,Netbeans會及時給你提示。只需跟着Netbeans的提示,就能很容易地把匿名類轉換成lambda表達式。此外,如果你喜歡閱讀,那麼記得看一下Java 8的lambdas,實用函數式編程這本書(Java 8 Lambdas, pragmatic functional programming),作者是Richard Warburton,或者也可以看看Manning的Java 8實戰(Java 8 in Action),這本書雖然還沒出版,但我猜線上有第一章的免費pdf。不過,在你開始忙其它事情之前,先回顧一下Java 8的lambda表達式、默認方法和函數式接口的重點知識。

1)lambda表達式僅能放入如下代碼:預定義使用了 @Functional 註釋的函數式接口,自帶一個抽象函數的方法,或者SAM(Single Abstract Method 單個抽象方法)類型。這些稱爲lambda表達式的目標類型,可以用作返回類型,或lambda目標代碼的參數。例如,若一個方法接收Runnable、Comparable或者 Callable 接口,都有單個抽象方法,可以傳入lambda表達式。類似的,如果一個方法接受聲明於 java.util.function 包內的接口,例如 Predicate、Function、Consumer 或 Supplier,那麼可以向其傳lambda表達式。

2)lambda表達式內可以使用方法引用,僅當該方法不修改lambda表達式提供的參數。本例中的lambda表達式可以換爲方法引用,因爲這僅是一個參數相同的簡單方法調用。

list.forEach(n -> System.out.println(n)); 
list.forEach(System.out::println);  // 使用方法引用

然而,若對參數有任何修改,則不能使用方法引用,而需鍵入完整地lambda表達式,如下所示:

list.forEach((String s) -> System.out.println("*" + s + "*"));

事實上,可以省略這裏的lambda參數的類型聲明,編譯器可以從列表的類屬性推測出來。

3)lambda內部可以使用靜態、非靜態和局部變量,這稱爲lambda內的變量捕獲。

4)Lambda表達式在Java中又稱爲閉包或匿名函數,所以如果有同事把它叫閉包的時候,不用驚訝。

5)Lambda方法在編譯器內部被翻譯成私有方法,並派發 invokedynamic 字節碼指令來進行調用。可以使用JDK中的 javap 工具來反編譯class文件。使用 javap -p 或 javap -c -v 命令來看一看lambda表達式生成的字節碼。大致應該長這樣:

private static java.lang.Object lambda$0(java.lang.String);

6)lambda表達式有個限制,那就是隻能引用 final 或 final 局部變量,這就是說不能在lambda內部修改定義在域外的變量。

List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { factor++; });
Compile time error : "local variables referenced from a lambda expression must be final or effectively final"

另外,只是訪問它而不作修改是可以的,如下所示:

List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { System.out.println(factor*element); });

輸出:

4
6
10
14

因此,它看起來更像不可變閉包,類似於Python。

以上就是Java 8的lambda表達式的全部10個例子。此次修改將成爲Java史上最大的一次,將深遠影響未來Java開發者使用集合框架的方式。我想規模最相似的一次修改就是Java 5的發佈了,它帶來了很多優點,提升了代碼質量,例如:泛型、枚舉、自動裝箱(Autoboxing)、靜態導入、併發API和變量參數。上述特性使得Java代碼更加清晰,我想lambda表達式也將進一步改進它。我在期待着開發並行第三方庫,這可以使高性能應用變得更容易寫。

發佈了14 篇原創文章 · 獲贊 5 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章