Java 自學筆記 09 - Stream API
Java Stream
Stream 是一個元素形成的隊列,其來源 (Source) 可以是集合或數組,Stream 本身並不會存儲元素,其操作也不會影響到原始來源,單純以數據流的方式來處理數據,依性質可以分為兩類:
- 中間操作 (Intermediate Operations)
Stream 的中間步驟,一個 Stream 可以有多個中間操作,會返回一個新的 stream,屬於惰性求值 (Lazy Evaluation),意思是不會馬上計算結果,而是等到終端操作時才一併計算。 - 終端操作 (Terminal Operations)
Stream 的最後一步,一個 stream 只能有一個終端操作,會產生一個新的集合或值。
創建 Stream
1 | // array to stream |
Parallel Stream
Stream 支援併行流 (parallelStream),底層默認使用 ForkJoinPool,所謂的 ForkJoinPool 其實就是 divide and conquer 算法,將任務不斷拆分成小任務處理,能充分利用 CPU 的效能,細節可以參考: ForkJoin Pool
可以直接創建 Parallel Stream 也可以從 Stream 轉換:1
2numbers.parallelStream(); // or
numbers.stream().parallel();
遍歷/匹配 (foreach/find/match)
findFirst、findAny 等操作返回的為一個 Optional 物件,用來表示一個可能為 null 的值,常用方法為:
- get():获取 Optional 对象中的值,如果值为 null,则抛出 NoSuchElementException 异常。
- orElse(T other):获取 Optional 对象中的值,如果值为 null,则返回指定的默认值。
- isPresent():判断 Optional 对象是否包含值。
- ifPresent(Consumer<? super T> consumer):如果 Optional 对象包含值,则执行指定的操作。
1 | // forEach |
篩選 (filter)
1 | numbers.stream() |
映射 (map/flatMap)
- map: 把一個元素映射到一個新的元素
- flatMap: 把一個元素映射到一個新的 stream
1 | numbers.stream() |
假設要將 {"Hello", "World"}
轉換成 {"H", "e", "l", "o", "W", "r", "d"}
,那我們不能直接用 map(s->s.split(""))
,因為 map 回傳的是一個元素,所以會是 String[]
,這時候就可以改用flatMap:1
2
3
4
5
6helloworld = {"H", "e", "l", "o", "W", "r", "d"};
helloworld.stream()
.flatMap(word-> Arrays.stream(word.split("")))
.distinct()
.collect(Collectors.toList());
歸約 (reduce)
- identity: 初始值,沒設置就是用第一筆資料當初始值。
- accumulator: 累加器函數,第一個參數是累加值,第二個參數是當前值。
- combiner: 組合器函數,在 parallel 情況下用來合併各個線程的結果。
accumulator
1 | // Optional<T> reduce( |
identity + accumulator
1 | // T reduce( |
identity + accumulator + combiner
1 | // <U> U reduce( |
output:1
2
3
4
5acc: 0, x: 2 // 各線程
acc: 0, x: 1
acc: 0, x: 3
comb: 2, x: 3 // 合併線程
comb: 1, x: 5
可以發現每個元素都被當作是一個新的線程了 (acc 都是 0),等於 accumulator 根本沒派上用場,事實上,accumulator 在這裡的作用反而更像是一個中介的轉換器,負責將原本的類型 T 轉換到 U,可以參考: Why is a combiner needed for reduce method that converts type in java 8
收集 (collect)
collect 的目的是用來將 stream 轉存為其他資料型態。
stream to collections
1 | numbers.stream() |
partitioningBy / groupingBy
- partitioningBy: 分為兩群 (
Map<boolean, List<T>>
) - groupingBy: 分為多群 (
Map<T, List<T>>
)
1
2
3
4numbers.stream()
.collect(Collectors.partitioningBy(x -> x>2));
numbers.stream()
.collect(Collectors.groupingBy(x -> x%4));
joining (String)
1 | String joined = numbers.stream() |
統計 (max/min/sum/average/count)
max / min
max / min 相關的操作都是返回 Optional 物件。1
2
3
4
5
6
7
8
9
10
11
12
13
14// int
Optional<Integer> max = numbers.stream()
.max(Integer::compareTo);
max.get();
// String
names.stream()
.min(Comparator.comparing(String::valueOf))
.get();
// stream
numbers.stream()
.collect(Collectors.maxBy(Integer::compareTo))
.get();
sum / average / count
注意 average 回傳的是 double1
2
3
4
5
6
7
8
9
10
11// sum
numbers.stream()
.collect(Collectors.summingInt(x->x));
// average
double average = numbers.stream()
.collect(Collectors.averagingInt(x->x));
// count
names.stream().count();
names.stream().collect(Collectors.counting());
參考資料
Java 8 Stream | 菜鸟教程
Java8 Stream 流的创建、筛选、映射、排序、归约、分组、聚合、提取与组合、收集、接合、foreach遍历
java8中stream的map和flatmap的理解
Java 8系列之Stream中万能的reduce