Java中的Lambda表達式
Java15馬上就要釋出了,但是大部分的Java開發者用的還是Java8,盡管Java8已經釋出了很多年了,但是很多開發者還以和老版本jdk不相容、不好調試等理由,還沒有用過Lambda。根據2020JVM生态報告,隻有3%的程式用的是1.7或者更老的版本,剩下都是使用Java8或者更新的版本。是以以後寫的代碼大機率是不會運作到老的jdk上的。是以趕緊掌握Lambda吧,學會之前你會很排斥Lambda的寫法,學會之後“真香”!
Lambda表達式是java8的最重要的新功能。Lambda 表達式賦予了 Java 程式員相較于其他函數式程式設計語言缺失的特性。
本篇文章會涉及Lambda相關的所有知識,包括:Lambda文法、函數式接口的使用、方法的引用、Stream流式處理。
一、Lambda初體驗
Lambda表達式可以簡單的了解就是隻有一個抽象函數的接口實作,有了它就可以告别匿名内部類,代碼看起來更簡潔易懂。話不多說,先看個例子:
public class LambdaDemo { public static void main(String[] args) { List languages = Arrays.asList("java", "c", "c++", "python"); //普通的方法按照長度排序 Collections.sort(languages, new Comparator() { @Override public int compare(String o1, String o2) { return o1.length() - o2.length(); } }); for (String language : languages) { System.out.println(language); } //使用lambda表達式排序 Collections.sort(languages,(a,b)->a.length()-b.length()); languages.forEach(System.out::println); }}
上面的代碼是調用Collections中的sorts方法對一個List進行排序:可以看到第二個參數傳入的是一個Comparator接口。
public static void sort(List list, Comparator super T> c) {list.sort(c);}
我們再來看看接口的實作:是一個使用@FunctionalInterface注解注釋的接口,這個注釋代表這是一個函數式接口,即隻有一個函數的接口
@FunctionalInterfacepublic interface Comparator { int compare(T o1, T o2); //其他方法都是有預設實作,或者Object中的方法}
其中第一種正常方法就是傳入一個匿名内部類實作這個接口,然後使用循環去周遊這個清單。
第二種方法是将匿名内部類使用一個 Lambda表達式代替,并使用::來表示方法引用。
很明顯第二種會比第一種更加簡潔、易讀(了解Lambda的前提)。
二、Lambda表達式的幾種寫法:
感受了Lambda的優勢之後,我們來詳細看下Lambda的幾種文法:
public class LambdaDemo2 { public static void main(String[] args) { //1、普通的匿名内部類 UserService userService1 = new UserService() { @Override public int insert(User user) { return 1; } }; System.out.println(userService1.insert(new User())); //2、使用lambda表達式代替匿名内部類 UserService userService2 = (User user) -> {return 2;}; System.out.println(userService2.insert(new User())); //3、()中的入參可以不寫類型,會自動比對 UserService userService3 = (user) -> {return 3;}; System.out.println(userService3.insert(new User())); //4、如果函數體隻有一行,可以去掉大括号,如果是return語句,還可以省略return UserService userService4 = (user) -> 4; System.out.println(userService4.insert(new User())); } interface UserService { int insert(User user); } static class User { int name; }}
結果
1234
Lambda表達式的文法是一個能省即省的過程,看完下面的過程就對Lambda表達式的文法有了一定的了解。
//最完整文法(type1 arg1, type2 arg2...) -> { doSomeThing();return 1;}//參數的類型可以省略,java會根據上下文推斷(arg1, arg2...) -> { doSomeThing();return 1;}//沒有參數的就小括号中為空() -> { doSomeThing();return 1;}//如果函數體隻有一行就可以省略大括号() -> doSomeThing()//如果這一行是return語句,return也可以省略() -> 1
至此,這就是一個最簡單的Lambda表達式,它代表一個沒有入參,傳回值為1的函數,等同于
void fun() {
return 1;
}
三、Java中提供的函數式接口
講完了Lambda的文法,我們再來詳細的看下java中的函數式接口。
函數式接口:有且隻有一個函數的接口。
JDK1.8之前就出現了一些符合函數式接口定義的接口:
java.lang.Runnablejava.util.concurrent.Callablejava.security.PrivilegedActionjava.util.Comparatorjava.io.FileFilterjava.nio.file.PathMatcherjava.lang.reflect.InvocationHandlerjava.beans.PropertyChangeListenerjava.awt.event.ActionListenerjavax.swing.event.ChangeListener
JDK1.8之後,又添加了一組函數式接口:
java.util.function.*例如:java.util.function.Function(一個輸入、一個輸出)java.util.function.Supplier(沒有輸入、一個輸出)java.util.function.Consumer(一個輸入、沒有輸出)java.util.function.BiConsumer(兩個輸入、沒有輸出)...
使用例子:
public class LambdaDemo3 { /** * java中提供了一系列的函數式接口,相當于把一個函數指派給一個變量 * @param args */ public static void main(String[] args) { //代表一個輸入一個輸出的函數 Function func = (s)->s.length(); System.out.println(func.apply("aaa")); //代表沒有輸入,一個輸出的函數 Supplier supp = ()-> "aaa"; System.out.println(supp.get()); //代表一個輸入,沒有輸出的函數 Consumer consumer = (s)-> System.out.println(s); consumer.accept("aaa"); //代表兩個輸入、沒有輸出 BiConsumer biConsumer = (s,i)-> System.out.println(s+i); biConsumer.accept("aaa",1); }}
說白了,就是将函數指派給一個變量,使用這個變量中的方法就可以調用這個函數。
函數式接口一般都會加上@FunctionalInterface注解,
@FunctionalInterface注解是對函數式接口的辨別,他的作用是對接口進行編譯級别的檢查,如果一個接口使用了這個注解,但是寫了兩個抽象方法,會出現編譯錯誤。
四、方法的引用
方法的引用是用來直接通路類或者執行個體的已經存在的方法或者構造方法,方法引用提供了一種引用而不執行的方式。::雙冒号作為方法引用的符号。
聽起來很高大上,說白了就是我們上一個章節的再次簡略寫法:
public class LambdaDemo5 { public static void main(String[] args) { //Lambda表達式的寫法 Function func = (s)->Test.getLength(s); System.out.println(func.apply("aaa")); //方法引用寫法 Function func2 = Test::getLength; System.out.println(func2.apply("aaa")); } static class Test { public static int getLength(String s) { return s.length(); } }}
可以看到就是把Lambda表達式再做了一個簡化,除了靜态方法,它還可以表示一下四種:
- 靜态方法引用
(args)->類名.staticMethod(args)
類名::staticMethod
- 執行個體方法引用
(args)->執行個體.instMethod(args)
執行個體::instMethod
- 對象方法引用
(inst,args)->類名.instMethod(args)
類名::執行個體方法
- 構造方法引用
(Args)->new 類名(args)
(類名::new)
五、Lambda 表達式與匿名類的差別
使用匿名類與 Lambda 表達式的一大差別在于關鍵詞的使用。對于匿名類,關鍵詞 this 解讀為匿名類,而對于 Lambda 表達式,關鍵詞 this 解讀為寫 Lambda 的外部類。
Lambda 表達式與匿名類的另一不同在于兩者的編譯方法。Java 編譯器編譯 Lambda 表達式并将他們轉化為類裡面的私有函數,它使用 Java 7 中新加的 invokedynamic 指令動态綁定該方法,關于 Java 如何将 Lambda 表達式編譯為位元組碼,Tal Weiss 寫了一篇很好的文章。
六、Stream API
你還在用各種for循環來周遊集合嗎?Stream 是 Java 8 中集合資料處理的利器,很多本來複雜、需要寫很多代碼的方法,比如過濾、分組等操作,往往使用 Stream 就可以在一行代碼搞定。這也是Lambda表達式用的最多的地方。這種代碼更多地表達了業務邏輯的意圖,而不是它的實作機制。易讀的代碼也易于維護、更可靠、更不容易出錯。
Stream API初體驗
舉個例子:
public static void main(String[] args) { List nameList = new ArrayList(); nameList.add("張三"); nameList.add("李四"); nameList.add("王五"); nameList.add("王五"); //計算有幾個王五 //正常做法 int count1 = 0; for (String name: nameList) { if(name.equals("仁昌居士")) count1++; } System.out.println(count1); //流式做法 long count2 = nameList.stream() .filter(name->name.equals("王五")) .count(); }
上述代碼實際是三步:
第一步:nameList建立了一個Stream執行個體,
第二步:用fliter操作符過濾找出為“王五”的name,并轉換成另外一個Stream,
第三步:把Stream的裡面包含的内容按照某種算法來成型成一個值,代碼中式用count操作符計算有幾個這樣的name。
是不是代碼邏輯很清晰?再次強調了這種代碼更多地表達了業務邏輯的意圖,而不是它的實作機制。
附上一個很全的Stream使用執行個體(比較java7和java8不同寫法的特點):
public class StreamDemo { public static void main(String args[]) { List strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); System.out.println("原始清單: " + strings); // 計算空字元串的個數 System.out.print("使用 Java 7: "); long count = getCountEmptyStringUsingJava7(strings); System.out.println("空字元數量為: " + count); System.out.print("使用 Java 8: "); count = strings.stream().filter(string -> string.isEmpty()).count(); System.out.println("空字元串數量為: " + count); // 删除空字元串 System.out.print("使用 Java 7: "); List filtered = deleteEmptyStringsUsingJava7(strings); System.out.println("篩選後的清單: " + filtered); System.out.print("使用 Java 8: "); filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("篩選後的清單: " + filtered); // 删除空字元串,并使用逗号把它們合并起來 System.out.print("使用 Java 7: "); String mergedString = getMergedStringUsingJava7(strings, ", "); System.out.println("合并字元串: " + mergedString); System.out.print("使用 Java 8: "); mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("合并字元串: " + mergedString); List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); // 擷取清單元素平方數 System.out.print("使用 Java 7: "); List squaresList = getSquares(numbers); System.out.println("平方數清單: " + squaresList); System.out.print("使用 Java 8: "); squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList()); System.out.println("平方數清單: " + squaresList); //對List進行統計計算 System.out.println(" 使用 Java 7: "); List integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19); System.out.println("清單: " + integers); System.out.println("清單中最大的數 : " + getMax(integers)); System.out.println("清單中最小的數 : " + getMin(integers)); System.out.println("所有數之和 : " + getSum(integers)); System.out.println("平均數 : " + getAverage(integers)); System.out.println("随機數: "); Random random = new Random(); for (int i = 0; i < 10; i++) { System.out.print(random.nextInt()); } System.out.println(); System.out.println(" 使用 Java 8: "); System.out.println("清單: " + integers); IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("清單中最大的數 : " + stats.getMax()); System.out.println("清單中最小的數 : " + stats.getMin()); System.out.println("所有數之和 : " + stats.getSum()); System.out.println("平均數 : " + stats.getAverage()); System.out.println("随機數: "); random.ints().limit(10).sorted().forEach(System.out::print); System.out.println(); // 并行處理 count = strings.parallelStream().filter(string -> string.isEmpty()).count(); System.out.println(" 空字元串的數量為: " + count); } private static int getCountEmptyStringUsingJava7(List strings) { int count = 0; for (String string : strings) { if (string.isEmpty()) { count++; } } return count; } private static int getCountLength3UsingJava7(List strings) { int count = 0; for (String string : strings) { if (string.length() == 3) { count++; } } return count; } private static List deleteEmptyStringsUsingJava7(List strings) { List filteredList = new ArrayList(); for (String string : strings) { if (!string.isEmpty()) { filteredList.add(string); } } return filteredList; } private static String getMergedStringUsingJava7(List strings, String separator) { StringBuilder stringBuilder = new StringBuilder(); for (String string : strings) { if (!string.isEmpty()) { stringBuilder.append(string); stringBuilder.append(separator); } } String mergedString = stringBuilder.toString(); return mergedString.substring(0, mergedString.length() - 2); } private static List getSquares(List numbers) { List squaresList = new ArrayList(); for (Integer number : numbers) { Integer square = new Integer(number.intValue() * number.intValue()); if (!squaresList.contains(square)) { squaresList.add(square); } } return squaresList; } private static int getMax(List numbers) { int max = numbers.get(0); for (int i = 1; i < numbers.size(); i++) { Integer number = numbers.get(i); if (number.intValue() > max) { max = number.intValue(); } } return max; } private static int getMin(List numbers) { int min = numbers.get(0); for (int i = 1; i < numbers.size(); i++) { Integer number = numbers.get(i); if (number.intValue() < min) { min = number.intValue(); } } return min; } private static int getSum(List numbers) { int sum = (int) (numbers.get(0)); for (int i = 1; i < numbers.size(); i++) { sum += (int) numbers.get(i); } return sum; } private static int getAverage(List numbers) { return getSum(numbers) / numbers.size(); }}
運作結果:
原始清單: [abc, , bc, efg, abcd, , jkl]使用 Java 7: 空字元數量為: 2使用 Java 8: 空字元串數量為: 2使用 Java 7: 篩選後的清單: [abc, bc, efg, abcd, jkl]使用 Java 8: 篩選後的清單: [abc, bc, efg, abcd, jkl]使用 Java 7: 合并字元串: abc, bc, efg, abcd, jkl使用 Java 8: 合并字元串: abc, bc, efg, abcd, jkl使用 Java 7: 平方數清單: [9, 4, 49, 25]使用 Java 8: 平方數清單: [9, 4, 49, 25] 使用 Java 7: 清單: [1, 2, 13, 4, 15, 6, 17, 8, 19]清單中最大的數 : 19清單中最小的數 : 1所有數之和 : 85平均數 : 9随機數: -35366133-1298526701-1266337525-20818345831538108643-5821116191889783487-8403190421768486081-155357501 使用 Java 8: 清單: [1, 2, 13, 4, 15, 6, 17, 8, 19]清單中最大的數 : 19清單中最小的數 : 1所有數之和 : 85平均數 : 9.444444444444445随機數: -2019356909-1502217945-1487693314-105281026276659503433663505727857020178489725118234876092120424373 空字元串的數量為: 2
好!至此Java8中的Lambda表達式相關的所有知識都已經介紹完了,是不是很香呢?趕緊在項目中用起來吧!
版權聲明: 本文為 InfoQ 作者【MJ】的原創文章。
原文連結:【https://xie.infoq.cn/article/1a9f7780c3d7445934c099702】。