# Streams API
`java8`添加了眾多函數(shù)式編程功能。作用有二:(1)代碼簡介,使用`stream`告別`for`循環(huán)。(2)多核友好,直接調(diào)用`parallel()`編寫并行程序。
`stream`不是某種數(shù)據(jù)結(jié)構(gòu),只是一種數(shù)據(jù)源(數(shù)組,容器或I/O channel等)的視圖。`stream`一般調(diào)用對應(yīng)的工具方法創(chuàng)建而不是手動創(chuàng)建。

如圖所示,3種`stream`結(jié)構(gòu)均繼承自`BaseStream`,其中`IntStream, LongStream, DoubleStream`對應(yīng)三種基本類型(`int, long, double`,注意不是包裝類型),`Stream`對應(yīng)所有剩余類型的*stream*視圖。為不同的數(shù)據(jù)類型設(shè)置不同的`stream`的好處就是:(1)提高性能,(2)增加特定接口函數(shù)。這里不將`IntStream`設(shè)置成`Stream`子接口的原因是二者的方法名大多相同,但是返回類型卻不同。
`Stream`和`collections`的區(qū)別:
- **無存儲**,`Stream`不是數(shù)據(jù)結(jié)構(gòu),也不是數(shù)據(jù)源,只是數(shù)據(jù)源的一個視圖。
- **為函數(shù)式編程而生**:對*stream*的任何修改都不會修改背后的數(shù)據(jù)源,而是產(chǎn)生他一個新的`stream`。
- **惰式執(zhí)行**。*stream*上的操作并不會立即執(zhí)行,只有等真正需要結(jié)果的時候才會執(zhí)行。
- **可消費性**。*stream*只能被“消費”一次,一旦遍歷過就會失效,就像容器的迭代器那樣,想要再次遍歷必須重新生成。
## stream常見接口方法
`stream`的接口分為中間操作和結(jié)束操作,最簡單的區(qū)分方式就是看返回值:中間操作絕大多數(shù)都是返回`stream`。
| 操作類型 | 接口方法? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
| ------------- | ------------------------------------------------------------ |
| 中間操作 | concat() distinct() filter() flatMap() limit() map() peek()? skip() sorted() parallel() sequential() unordered() |
| 結(jié)束操作 | allMatch() anyMatch() collect() count() findAny() findFirst()? forEach() forEachOrdered() max() min() noneMatch() reduce() toArray() |
```java
// void forEach(Consumer<? super E> action)
// forEach是結(jié)束方法,代碼立即執(zhí)行,遍歷元素執(zhí)行action操作
stream.forEach(str -> doAction(str));
// Stream<T> filter(Predicate<? super T> predicate)
// filter是中間操作,不會立即執(zhí)行,作用是返回只包含predicate條件元素的stream
stream.filter(str -> str.length() == 3).forEach(str -> doAction(str));
// Stream<T> distinct()
// 中間操作,返回去除重復(fù)元素之后的stream
stream.distinct().forEach( .......... );
// Stream<T> sorted(), Stream<T> sorted(Comparator<? super T> comparator)
// 中間操作,返回自然排序或自定義排序比較器的排序結(jié)果
stream.sort((str1, str2) -> str1.length() - str2.length()).forEach(...);
// <R> Stream<R> map(Function<? super T,? extends R> mapper)
// 返回對當前所有元素執(zhí)行mapper操作之后的結(jié)果的stream
stream.map(str -> str.toUpperCase()).forEach(...);
// <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
// 對每個元素執(zhí)行mapper操作,將所有mapper返回的stream元素組成一個新的stream作為返回結(jié)果
// Stream<List<Integer>> --flatMap--> Stream<Integer>
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2), Arrays.asList(3, 4, 5));
stream.flatMap(list -> list.stream()).forEach(...); // return [1,2,3,4,5]
```
## Stream規(guī)約操作
`stream`的規(guī)約操作(reduction operation)也稱為折疊操作(fold),通過某個鏈接動作將所有元素匯總成一個結(jié)果。例如:元素求和,最大最小值等。`stream`的通用規(guī)約操作是`reduce()`和`collect()`,為了簡化書寫設(shè)計的專用規(guī)約操作為:`sum()`, `max()`, `min()`, `count()`等。
### reduce
`reduce`操作實現(xiàn)從一組元素生成一個值,`sum(), max(), min(), count()`都是`reduce`操作。多個參數(shù)的作用只是為了指明初始值(參數(shù)*identity*),或者是指定并行執(zhí)行時多個部分結(jié)果的合并方式(參數(shù)*combiner*)。
```java
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
// 求最長的單詞,Optinal是為了避免null帶來的麻煩
// Optional<String> longest = stream.max((s1, s2) -> s1.length()-s2.length());
Optional<String> longest = stream.reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2);
// 求一組單詞的長度之和
Integer lengthSum = stream.reduce(0, // 初始值
? ? ? ? (sum, str) -> sum+str.length(), // 累加器,將字符串映射成長度并相加
? ? ? ? (a, b) -> a+b); // 部分和拼接器,并行執(zhí)行時才會用到
```
### collect
?? ? 如果某個功能在`stream`的接口中沒有找到,有很大的可能性是通過`collect()`實現(xiàn)。
#### 默認方法和靜態(tài)方法
```java
// java8 中允許接口加入具體的方法,分為default方法和static方法。identify()是Function接口的靜態(tài)方法,返回一個和輸入一樣的Lambda對象,等價于`t -> t`。java的default方法是為了避免修改接口之后讓所有的實現(xiàn)類都需要重新實現(xiàn),因為如果直接在接口類中添加抽象方法會導(dǎo)致接口的類需要重新實現(xiàn),添加default和static方法則無影響(static方法是為了避免再寫專門的工具類)。
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));
```
#### 方法引用
方法引用的形式就是`String::length`。分為靜態(tài)方法引用(`Integer::sum`),引用某個對象的方法(`list::add`),引用某各類的方法(`String::length`),引用構(gòu)造方法(`HashMap::new`)。
#### 收集器
收集器(`Collector`)是為`stream.collect()`方法量身打造的工具接口(類),將`stream`轉(zhuǎn)換成新的容器需要:(1)指定目標容器;(2)指定新元素添加到目標容器中的方法;(3)如果需要并行規(guī)約,需要指定多個結(jié)果合并的方式。`collect()`方法定義的三個參數(shù)依次對應(yīng)上面的3條規(guī)則:
```java
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
// stream轉(zhuǎn)list的方式
list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll); // 方式1
// 通常情況不需要手動指定3個參數(shù),直接調(diào)用collect(Collector<? super T,A,R> collector)的方法即可
list = stream.collect(Collectors.toList()); //方式2
```
方式2可以滿足絕大部分需求,但是方式2的返回結(jié)果是接口類型,實際上并不知道類庫實際選擇的容器類型是什么,可以通過`Collectors.toCollection(Supplier<C> collectionFactory`指定容器類型。
```java
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));// (3)
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));// (4)
```
#### 使用collect生成Map
`stream`依賴的數(shù)據(jù)源是數(shù)組等容器,但不能是`Map`,反過來使用`stream`生成`map`是可行的。
方式1:直接使用`Collectors.toMap()`生成的收集器,需要用戶指定如何生成`key`和`value`:
```java
// 使用toMap()統(tǒng)計學(xué)生GPA
Map<Student, Double> studentToGPA =
? ? students.stream().collect(Collectors.toMap(Function.identity(),// 如何生成key
```
方式2:使用`partitioningBy()`生成的收集器,這種情況適用于將`Stream`中的元素依據(jù)某個二值邏輯(滿足條件,或不滿足)分成互補相交的兩部分,比如男女性別、成績及格與否等。
```java
// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream()
? ? ? ? .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
```
情況3:使用`groupingBy()`生成的收集器。跟`SQL`中的`group by`語句類似,這里的`groupingBy()`也是按照某個屬性對數(shù)據(jù)進行分組,屬性相同的元素會被對應(yīng)到`Map`的同一個`key`上。
```java
// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
? ? ? ? ? ? .collect(Collectors.groupingBy(Employee::getDepartment));
```
`groupBy`除了對分組的基本用法以外,還有增強型。增強版的`groupingBy()`允許我們對元素分組之后再執(zhí)行某種運算,比如求和、計數(shù)、平均值、類型轉(zhuǎn)換等。這種先將元素分組的收集器叫做**上游收集器**,之后執(zhí)行其他運算的收集器叫做**下游收集器**(*downstream Collector*)。
```java
/***********************************難度較大,仔細看*****************************************/
// 使用下游收集器統(tǒng)計每個部門的人數(shù)
Map<Department, Integer> totalByDept = employees.stream()
? ? ? ? ? ? ? ? ? ? .collect(Collectors.groupingBy(Employee::getDepartment,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Collectors.counting()));// 下游收集器
// 按照部門對員工分布組,并只保留員工的名字
Map<Department, List<String>> byDept = employees.stream()
? ? ? ? ? ? ? ? .collect(Collectors.groupingBy(Employee::getDepartment,
? ? ? ? ? ? ? ? ? ? ? ? Collectors.mapping(Employee::getName,// 下游收集器
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Collectors.toList())));// 更下游的收集器
```
#### collect()做字符串join
使用`Collectors.joining()`生成的收集器替代`for`循環(huán)拼接字符串,可以使用3種方式:
```java
// 使用Collectors.joining()拼接字符串
Stream<String> stream = Stream.of("I", "love", "you");
//String joined = stream.collect(Collectors.joining());// "Iloveyou"
//String joined = stream.collect(Collectors.joining(","));// "I,love,you"
String joined = stream.collect(Collectors.joining(",", "{", "}"));// "{I,love,you}"
```