2019-12-05

# 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}"

```

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容