Lambda
前言
當我們想要使用一個線程,我們其實並不想真的創建一個匿名內部類對象。我們只是爲了做這件事情而不得不創建一個對象。我們真正希望做的事情是:將 run 方法體內的代碼傳遞給 Thread 類。
傳統的使用一個線程
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多線程任務執行!");
}
}).start();
}
}
對於 Runnable 的匿名內部類用法,可以分析出幾點內容:
- Thread 類需要 Runnable 接口作爲參數,其中的抽象 run 方法是用來指定線程任務內容的核心;
- 爲了指定 run 的方法體,不得不需要 Runnable 接口的實現類;
- 爲了省去定義一個 RunnableImpl 實現類的麻煩,不得不使用匿名內部類;
- 必須覆蓋重寫抽象 run 方法,所以方法名稱、方法參數、方法返回值不得不再寫一遍,且不能寫錯;
- 而實際上,似乎只有方法體纔是關鍵所在
使用Lambda表達式寫法
public class Test {
public static void main(String[] args) {
new Thread(()->System.out.println("多線程任務執行!")).start();
}
}
這段代碼和上面的傳統寫法功能一樣,但卻是肉眼可見的簡單!!
使用Lambda表達式的前提
那程序在滿足什麼條件的時候纔可以使用Lambda表達式呢??
- 使用Lambda必須具有接口,且要求接口中有且僅有一個抽象方法。 無論是JDK內置的 Runnable 、Comparator 接口還是自定義的接口,只有當接口中的抽象方法存在且唯一時,纔可以使用Lambda。
- 使用Lambda必須具有上下文推斷。 也就是方法的參數或局部變量類型必須爲Lambda對應的接口類型,才能使用Lambda作爲該接口的實例。
備註:有且僅有一個抽象方法的接口,稱爲“函數式接口”。
Lambda表達式格式
一個參數 一個箭頭 一段代碼
(參數類型 參數名)->{代碼語句}//如果只有一條語句{}大括號也可以省略
格式說明:
- 小括號內的語法與傳統方法參數列表一致:無參數則留空;多個參數則用逗號分隔
- 中間的一個箭頭代表將前面的參數傳遞給後面的代碼;
- 大括號內的語法與傳統方法體要求基本一致。
Lambda表達式的省略規則
在Lambda標準格式的基礎上,使用省略寫法的規則爲:
- 小括號內參數的類型可以省略;
- 如果小括號內有且僅有一個參,則小括號可以省略;
- 如果大括號內有且僅有一個語句,則無論是否有返回值,都可以省略大括號、return關鍵字及語句分號。
通過這些省略規則,可以嘗試簡化 -----使用Collections的sort方法對集合字符串排序
Stream
前言
試想一下,如果希望對集合中的元素進行篩選過濾:
篩選出集合中所有姓張的人,在到所有姓張的人中篩選名字長度是3個字的!
傳統的方法你可能會這樣
import java.util.ArrayList;
import java.util.List;
public class Demo02NormalFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三丰");
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("張")) {
zhangList.add(name);
}
}
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
} for (String name : shortList) {
System.out.println(name);
}
}
}
代碼分析:
這段代碼中含有三個循環,每一個作用不同:
- 首先篩選所有姓張的人;
- 然後篩選名字有三個字的人;
- 最後進行對結果進行打印輸出。
每當我們需要對集合中的元素進行操作的時候,總是需要進行循環、循環、再循環。這是理所當然的麼?不是。循環是做事情的方式,而不是目的。另一方面,使用線性循環就意味着只能遍歷一次。如果希望再次遍歷,只能再使用另一個循環從頭開始。
當我們使用Stream後的代碼
import java.util.ArrayList;
import java.util.List;
public class Demo03StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三丰");
list.stream()
.filter(s -> s.startsWith("張"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
我丟!怎麼可以這麼簡單 ┗( ▔, ▔ )┛
直接閱讀代碼的字面意思就可以完美展示這段代碼的功能:獲取流、過濾姓張、過濾長度爲3、逐一打印。
獲取流方式
獲取的方法比較簡單
- 所有的 Collection 集合都可以通過 stream 默認方法獲取流;
- Stream 接口的靜態方法 of 可以獲取數組對應的流
java.util.Collection 接口中加入了default方法 stream 用來獲取流,所以其所有實現類均可獲取流。
List集合舉例
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
of 方法的參數其實是一個可變參數,所以支持數組。
舉例
String[] array = { "張無忌", "張翠山", "張三丰", "張一元" };
Stream<String> stream = Stream.of(array);
常用方法
終結方法:
返回值類型不再是 Stream 接口自身類型的方法,因此不再支持類似 StringBuilder 那樣的鏈式
調用。終結方法包括 count 和 forEach 方法
非終結方法:
返回值類型仍然是 Stream 接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其
餘方法均爲非終結方法。)
方法名 | 方法作用 | 方法種類 | 是否支持鏈式調用 |
---|---|---|---|
count | 統計個數 | 終結 | 否 |
forEach | 逐一處理 | 終結 | 否 |
filter | 過濾 | 函數拼接 | 是 |
limit | 取用前幾個 | 函數拼接 | 是 |
skip | 跳過前幾個 | 函數拼接 | 是 |
concat | 組合 | 函數拼接 | 是 |
來個綜合案例使用一下這些方法
/**
* 現在有兩個 ArrayList 集合存儲隊伍當中的多個成員姓名,依次進行以下若干操作步驟:
* 1. 第一個隊伍只要名字爲3個字的成員姓名;
* 2. 第一個隊伍篩選之後只要前3個人;
* 3. 第二個隊伍只要姓張的成員姓名;
* 4. 第二個隊伍篩選之後不要前2個人;
* 5. 將兩個隊伍合併爲一個隊伍;
* 7. 打印整個隊伍的信息。
*/
class Test{
public static void main(String[] args) {
//第一個大部隊
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1,"張三","李四","王老五","趙四兒");
//第二個大部隊
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2,"胡歌","李易峯","彭于晏","蔡徐坤");
//第一個隊伍只要名字爲3個字的成員姓名; 第一個隊伍篩選之後只要前3個人;
Stream<String> stream1 = list1.stream();
Stream<String> streamOne = stream1.filter(s -> s.length() == 3).limit(3);
//第二個隊伍只要姓張的成員姓名;第二個隊伍篩選之後不要前2個人;
Stream<String> stream2 = list2.stream();
Stream<String> streamTwo = stream2.filter(s -> s.startsWith("張")).skip(2);
//將兩個隊伍合併爲一個隊伍; 打印整個隊伍的信息。
Stream.concat(streamOne,streamTwo).forEach(s -> System.out.println(s));
}
}