章節目錄
1. 前言
談起這個Lambda表達式,我不禁想起了我的一個小故事,沒有它,我估計到現在都還不知道何爲Lambda表達式。
故事是這樣的:在大三上時,我面臨着考研Or就業的抉擇,於是,我去找了一位我校的老師,與他進行簡單地溝通了一下,他對我的情況作出了簡要分析,我也思索了良久。最後,我選擇了就業-----Java開發工程師。於是,他說:你想搞Java開發的話,Java基礎要好。當時,我自我感覺良好,就跟他謙虛地說:我覺得我的Java基礎還不錯。然後,他給我來了一句:你知道Lambda表達式(當時,我並不知道是這個東西)嗎?我有點懵,當時腦海中浮現的是數學中的“λ”,但這跟Java毫無血緣關係啊。我說:我沒聽說它。聽後,那位老師哈哈大笑。
過後,我到菜鳥教程網站上了解了一下:Java 8 Lambda 表達式。所以,這裏簡單地寫寫博客記錄一下。
2. 認識Lambda
2.1 Lambda簡介
Lambda 表達式(又名閉包)是 JDK8 的一個新特性,它:
- 允許把函數作爲一個方法的參數(函數作爲參數傳遞進方法中)
- 可以取代大部分的匿名內部類
- 更容易操作集合
Lambda表達式的寫法(上述說法可能比較抽象,下文會有案例)可以極大地優化代碼結構,使Java代碼看起來更優雅。
2.2 Lambda語法
Lambda表達式有三個部分:
- 參數列表 ()
- 箭頭 ->
- 主體 {}
語法格式:
(parameters) -> expression
或
(parameters) ->{ statements; }
語法格式簡要說明:
- 類型聲明(可選):不需要聲明參數類型,編譯器可以統一識別參數值
- 參數圓括號(可選):一個參數無需定義圓括號,但多個參數需要定義圓括號
- 大括號(可選):如果主體包含了一條語句,就不需要使用大括號
- 返回關鍵字(可選):如果主體只有一個表達式返回值。則編譯器會自動返回值,大括號需要指定明表達式返回了一個數值
2.3 Lambda表達式實例
下面是一些lambad表達式的簡單例子:
// 1. 不需要參數,返回值爲 5
() -> 5
// 2. 接收一個參數(數字類型),返回其2倍的值
x -> 2 * x
// 3. 接受2個參數(數字),並返回他們的差值
(x, y) -> x – y
// 4. 接受一個 string 對象,並在控制檯打印,不返回任何值(看起來像是返回void)
(String s) -> System.out.print(s)
2.4 自定義一個Lambda表達式
接下來自定義一個Lambda表達式,通過這個過程,可以進一步瞭解Lambda表達式。
對於一個Java變量,我們可以給其賦值:
如果你想把“一塊代碼”(函數)賦給一個Java變量,那你應該怎麼做呢?比如:我想把右邊一個函數doTest()賦值給一個Java變量aBlockOfCode
在Java8之前是做不到的。但在Java8問世之後,可以通過Lambda表達式做到
按照Lambad表達式的語法格式進行簡化,帶代碼看起來更優雅
其實,還可以優化,去掉參數的小括號(只有一個參數)
就這樣,我們成功地把一個函數賦給了一個Java變量。
但有一個問題:這個變量aBlockOfCode是什麼數據類型呢?
在Java8中,所有的Lambda的數據類型都是一個接口(很特殊),而Lambda表達式本身就是接口的實現。這樣說,或許比較抽象,接下來繼續看例子。
我們給上面的Java變量aBlockOfCode添加一個數據類型(接口)
這種只有一個接口函數(抽象函數)需要被實現的接口類型,我們叫它“函數式接口(下文會講)”。爲了避免他人在這個接口中增加接口函數導致其有多個接口函數需要被實現,變成"非函數接口”,我們可以在這個上面加上一個註解@FunctionalInterface, 這樣別人就無法在裏面添加新的接口函數(抽象函數)了:
所以,一個完整的Lambda表達式聲明:
2.5 Lambda表達式對接口的要求
2.5.1 函數式接口
雖然使用Lambda表達式可以對某些接口進行簡單的實現,但並不是所有的接口都可以使用Lambda表達式來實現。Lambda規定:接口中只能有一個需要被實現的方法(抽象方法),但可以有多個非抽象方法(靜態方法、默認方法)-----函數式接口。
JDK 1.8 之前已有的函數式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.util.Comparator
- …
JDK也新增了一些函數式接口。
2.5.2 註解@FunctionalInterface
@FunctionalInterface修飾函數式接口的,它要求接口中的抽象方法只有一個。此註解往往會和lambda表達式一起出現。接下來舉一個例子:
2.5.3 舉個例子
定義了一個函數式接口GreetingService 如下:
@FunctionalInterface
interface GreetingService {
void sayMessage(String message);
}
那麼就可以使用Lambda表達式來表示該接口的一個實現(注:JAVA 8 之前一般是用匿名類實現的):
GreetingService greetService = message -> System.out.println("Hello " + message);
3. 使用Lambda表達式
3.1 將Lambda作爲方法的參數,傳進方法中
函數式接口MyLambdaInterface
@FunctionalInterface
public interface MyLambdaInterface {
void doTest(String s);
}
Test
1.使用lambda表達式
public class Test {
public static void test(MyLambdaInterface myLambdaInterface, String s) {
myLambdaInterface.doTest(s);
}
public static void main(String[] args) {
// 直接將lambad表達式傳給了test()方法
test(s -> {System.out.println(s);}, "Hello World");
}
}
2.使用傳統方法
先定義一個函數式接口的實現類MyLambdaInterfaceImpl,並重寫方法doTest()
MyLambdaInterfaceImpl
public class MyLambdaInterfaceImpl implements MyLambdaInterface {
@Override
public void doTest(String s) {
System.out.println(s);
}
}
再實例化MyLambdaInterfaceImpl 對象,並傳入test()方法中
Test
public class Test {
public static void test(MyLambdaInterface myLambdaInterface, String s) {
myLambdaInterface.doTest(s);
}
public static void main(String[] args) {
// 此處也可以用匿名內部類實現
test(new MyLambdaInterfaceImpl(), "Hello World");
}
}
從代碼的簡潔度來說,lambda表達式比傳統的方法更簡潔、優雅
3.2 Lambda表達式替換匿名內部類
3.2.1 傳統方法創建線程
創建線程的方法很多,我知道的有四個:
- 繼承Thread類
- 實現Runnable接口
- 實現Callable接口
- 通過線程池創建
這裏創建線程就使用Runnable接口
3.2.1.1 使用外部類創建線程
自定義一個類並實現Runnable接口,重寫run()方法。
OuterClass
public class OuterClass implements Runnable {
@Override
public void run() {
// 打印當前線程名稱
System.out.println(Thread.currentThread().getName());
}
}
Test
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new OuterClass()).start();
}
}
}
創建兩個線程,並打印線程的名稱
3.2.1.2 使用匿名內部類創建線程
Test
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
}
3.2.2 Lambda表達式創建線程
Test
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}).start();
}
}
}
3.3 Lambda表達式操作集合
因爲操作集合的操作類型比較多,這裏就簡要舉2個簡單的例子
3.3.1 使用Lambda表達式遍歷集合
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6);
list.forEach(
e -> {
if (e % 2 == 0) {
System.out.println(e);
}
});
}
}
3.3.2 map()
map 方法用於映射每個元素到對應的結果,以下代碼片段使用 map 輸出了元素對應的平方數:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6);
List<Integer> collect = list.stream().map(e -> e * e).collect(Collectors.toList());
System.out.println(collect);
}
4. Lambda表達式中的閉包問題
此處的閉包問題指的是 變量作用域的問題,這種問題也會在匿名/局部內部類中存在(Java中的內部類瞭解一下)。lambda 表達式只能引用標記了 final 的外層局部變量,這就是說不能在 lambda 內部修改定義在域外的局部變量,否則會編譯錯誤。
如:
MyLambdaInterface接口上面有。
也可以直接在 lambda 表達式中訪問外層的局部變量:
public class Test {
public static void main(String[] args) {
String name = "zzc";
MyLambdaInterface myLambdaInterface = s -> {
System.out.println(s + name);
};
myLambdaInterface.doTest("hello ");
}
}
lambda 表達式的局部變量可以不用聲明爲 final,但是必須不可被後面的代碼修改(即隱性的具有 final 的語義),否則,會報錯
在 Lambda 表達式當中不允許聲明一個與局部變量同名的參數或者局部變量。
好了,今天的Lambda表達式跟大家分享到這兒了,希望大家能懂!~
【參考資料】
Lambda 表達式有何用處?如何使用?
菜鳥教程