【javaSE-語法】lambda表達式
- 1. 先回憶一下:
- 1.1 接口不能直接通過關鍵字new進行實例化
- 1.2 函數式接口
- 1.3 匿名內部類
- 1.31 匿名內部類在代碼中長啥樣?
- 1.32 構造一個新的對象與構造一個擴展了某類的匿名內部類的對象,兩者有什么區別?
- 1.33 如何調用匿名內部類對象中的成員
- 1.34 匿名內部類的變量捕獲
- 2. lambda表達式
- 2.1 語法
- 2.2 基本使用
- 2.3 lambda的變量捕獲
- 2.4 lambda在集合中的使用
- 2.41 Collection接口下的forEach()方法和lambda的夢幻聯動
- 2.42 List接口下的sort()方法和lambda的夢幻聯動
- 4. lambda表達式的優缺點
1. 先回憶一下:
1.1 接口不能直接通過關鍵字new進行實例化
-
接口是引用數據類型,代碼中使用interface定義一個接口。接口中的成員變量默認被
public static final
修飾,接口中的成員方法默認被public abstract
修飾,抽象方法沒有具體的實現。 -
接口中成員方法如果用defaul或者static修飾,該成員方法可以有具體的實現。
-
-
接口不能通過new關鍵字進行實例化:
-
類通過關鍵字implement實現接口,類實現接口后,要重寫接口中的所有抽象方法。通過實例化(實現了某接口的)類,間接實例化某接口。
-
-
接口間可以通過關鍵字extends進行拓展,即把多個接口合并在一起。
1.2 函數式接口
如果一個接口中有且只有一個抽象方法,那么該接口就是一個函數式接口。
- 在寫代碼時,如果在某個接口上聲明了
@FunctionalInterface
,表示該接口必須是一個函數式接口,否則程序編譯會報錯。 - 下面這種也是函數式接口:
1.3 匿名內部類
1.31 匿名內部類在代碼中長啥樣?
如果一個類定義在類的內部或者方法的內部,該類就是內部類。根據內部類在代碼中的位置,內部類可以分為成員內部類和局部內部類。成員內部類與外部類成員所處位置相同,局部內部類定義在方法內部。
匿名內部類和局部內部類一樣,都在方法內部,且匿名內部類必須實現接口或者繼承其他類。
我們在寫代碼的時候,如果想在方法內部創建一個類,但是不會用到該類的名字,此時就可以使用匿名內部類。如上述代碼中的兩個匿名內部類:
-
第一個匿名內部類的意思是:有一個沒有名字的類,該類實現了A接口,并且重寫了A接口中的testA方法。
-
如果用局部內部類實現,代碼如下:
-
第二個匿名內部類的意思是:有一個沒有名字的類,該類繼承了類B,但沒有進行擴展。(一般都要進行擴展,即添加新的成員,否則該匿名內部類無意義)。
-
如果用局部內部類實現,代碼如下:
1.32 構造一個新的對象與構造一個擴展了某類的匿名內部類的對象,兩者有什么區別?
如果構造參數列表的,結束小括號后面跟一個開始大括號,就是在定義匿名內部類對象。
1.33 如何調用匿名內部類對象中的成員
1.34 匿名內部類的變量捕獲
在匿名內部類中,不僅能訪問外部類的字段,還能訪問局部變量。但是無論是字段還是局部變量,都只能是一旦賦值絕不會改變的變量或者被final修飾的常量。
2. lambda表達式
lambda表達式是匿名內部類的簡化。說的再詳細一點,lambda表達式創建了一個類,實現了一個接口,重寫了接口的方法。
看下面代碼,在代碼中對象priorityQueue和對象priorityQueue1完全一樣,但是顯然,創建priorityQueue1的代碼要更加簡潔。這是因為創建priorityQueue的時候,構造方法的參數是一個匿名內部類,而創建priorityQueue1的時候,構造方法的參數一個lambda表達式。
所以,使用lambda表達式能讓能讓代碼更加簡潔,開發也隨之更加迅速,這就是我們學習lambda表達式的原因。
2.1 語法
lambda表達式的語法: (parameters) -> expression 或 (parameters) ->{ statements; }
- parameters是參數,根據實際情況,參數可以有,也可以沒有。這里的參數等同于方法的參數列表。
- 參數的數據類型可以明確聲明,也可以不聲明而是讓JVM推斷。如果要省略,每個參數的數據類型都要省略。
- 當只有一個沒有聲明類型的參數時,可以省略外面的小括號()。
expression
是表達式,是函數式接口里方法的實現。statements;
是代碼塊,是函數式接口里方法的實現。這里的代碼塊和表達式等同于方法里面的方法體。- 如果方法體中只有一條代碼,大括號{}可以省略
- 如果方法體中只有一條語句,且是return語句,大括號和關鍵字return可同時省略。
例如:
// 1. 不需要參數,返回值為 2
() -> 2
// 2. 接收一個參數(數字類型),返回其2倍的值
x -> 2 * x
// 3. 接受2個參數(數字),并返回他們的和
(x, y) -> x + y
// 4. 接收2個int型整數,返回他們的乘積
(int x, int y) -> x * y
// 5. 接受一個 string 對象,并在控制臺打印,不返回任何值(看起來像是返回void)
(String s) -> System.out.print(s)
2.2 基本使用
接下來跟著具體場景看看lambda表達式在代碼中究竟如何使用:
- 已知函數式接口NoParameterNoReturn,接口中的test()方法沒有參數,沒有返回值。如何調用接口中的test()方法呢?
運行結果:
- 下面是使用lambda表達式的更多例子:
public class Test {public static void main1(String[] args) {//如何調用接口NoParameterNoReturn中的test方法?//不使用lambda表達式(通過匿名內部類實現接口,重寫方法):NoParameterNoReturn noParameterNoReturn =new NoParameterNoReturn() {@Overridepublic void test() {System.out.println("test......");}};noParameterNoReturn.test();//使用lambda表達式NoParameterNoReturn noParameterNoReturn1 =()-> System.out.println("test......");//重寫接口中的方法,該方法沒有參數,沒有返回值noParameterNoReturn1.test();}public static void main2(String[] args) {//如何調用接口OneParameterNoReturn中的test方法?OneParameterNoReturn oneParameterNoReturn = (x)->{System.out.println(x);};//只有一個沒聲明類型的參數,小括號()可以省略;方法體中只有一條語句,{}可以省略oneParameterNoReturn.test(10);//如何調用接口MoreParameterNoReturn中的test()方法?test()方法無返回值但是有多個參數MoreParameterNoReturn moreParameterNoReturn =(int x,int y) -> {System.out.println(x+y);};moreParameterNoReturn.test(10,20);}public static void main(String[] args) {//如何調用接口NoParameterReturn中的test方法?test方法中有返回值,無參數NoParameterReturn noParameterReturn =() -> {return 10;};//return和{}可同時省略System.out.println(noParameterReturn.test());//如何調用接口OneParameterReturn中的test方法?test方法中有返回值,有一個參數OneParameterReturn oneParameterReturn = x -> x;oneParameterReturn.test(20);//傳20返回20//如何調用接口MoreParameterReturn中的test方法?test方法中有返回值,有多個參數MoreParameterReturn moreParameterReturn =(a,b) -> {return a*b;};moreParameterReturn.test(10,20);//返回200}
}
//無返回值無參數
@FunctionalInterface
interface NoParameterNoReturn {void test();
}
//無返回值一個參數
@FunctionalInterface
interface OneParameterNoReturn {void test(int a);
}
//無返回值多個參數
@FunctionalInterface
interface MoreParameterNoReturn {void test(int a,int b);
}
//有返回值無參數
@FunctionalInterface
interface NoParameterReturn {int test();
}
//有返回值一個參數
@FunctionalInterface
interface OneParameterReturn {int test(int a);
}
//有返回值多參數
@FunctionalInterface
interface MoreParameterReturn {int test(int a,int b);
}
所以如果想在代碼中使用lambda表達式,一定要清楚函數式接口中要被重寫的方法,該方法中是否有返回值,是否有參數。
2.3 lambda的變量捕獲
同匿名內部類的變量捕獲一樣,lambda表達式內不僅能訪問外部類的字段,還能訪問局部變量。但是無論是字段還是局部變量,都只能是一旦賦值絕不會改變的變量或者被final修飾的常量。
例如在下面代碼中:
在多線程編程中,經常會用到lambda表達式,尤其要注意變量捕獲這一點,如果在lambda表達式這種用了某變量,但在后續又修改了該變量,代碼就會出現問題。
2.4 lambda在集合中的使用
lambda表達式是JavaSE8中的一個重要的新特性。為了能讓Lambda和Java的集合類集更好的一起使用,集合當中,也新增了部分接口,以便與Lambda表達式對
接。
對應的接口 | 新增的方法 |
---|---|
Collection | removeIf() spliterator() stream() parallelStream() forEach() |
List | replaceAll() sort() |
Map | getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge() |
接下來會分別演示在Collection接口forEach() 方法中,在List接口sort()方法中,分別如何使用lambda表達式。
其他方法在遇到時,查看Java幫助手冊或查看對應源碼,同樣的方法學習掌握即可。
2.41 Collection接口下的forEach()方法和lambda的夢幻聯動
- 先找到Collection接口下forEach()方法中的函數式接口:
Collection接口拓展了Iterable接口,
forEach()方法在Iterable接口中,
forEach()方法的源碼如下:從源碼中看出,forEach()方法的功能就是遍歷當前集合中的元素,然后讓每個元素執行action.accept()操作。
forEach()方法的參數action的類型是一個函數式接口,其中的void accept(T t)抽象函數
,有一個參數無返回值。
所以當我們調用forEach()方法的時候,forEach()方法的參數是一個函數式接口action,我們需要實現該接口并且重寫接口中的accept()的方法。此時我們可以使用匿名內部類或者lambda表達式:
- 例如下列代碼:
2.42 List接口下的sort()方法和lambda的夢幻聯動
- 先找到List接口下sort()方法中涉及的函數式接口:
sort()方法的源碼如下:
從源碼中看出,sort()方法的功能就是先調用Arrays工具包中的sort方法,并依據比較器c的規則對當前容器元素進行排序。然后再對已經排好序的當前容器進行迭代操作。
sort方法的參數c的類型是一個函數式接口,其中的int compare(T o1, T o2)抽象函數,有兩個參數有返回值。
所以當我們調用sort()方法的時候,sort()方法的參數是一個函數式接口c,我們需要實現該接口并且重寫接口中的compare()的方法。此時我們可以使用匿名內部類或者lambda表達式:
2.例如下面代碼:
4. lambda表達式的優缺點
Lambda表達式的優點很明顯,在代碼層次上來說,使代碼變得非常的簡潔。缺點也很明顯,代碼不易讀。
優點:
- 代碼簡潔,開發迅速
- 方便函數式編程
- 非常容易進行并行計算
- Java 引入 Lambda,改善了集合操作
缺點:
- 代碼可讀性變差
- 在非并行計算中,很多計算未必有傳統的 for 性能要高
- 不容易進行調試
好了,本篇到這就結束了,相信讀完本篇的你已經能讀懂,會用lambda表達式了。最后祝你我早日成為技術流。
我是一朵忽明忽暗的云,點贊收藏加關注,我們一起進步!