# Stream流
stream流是JDK1.8的另外一個新特性,前面已經介紹了《Lambda》和一些《常用函數式接口》,接下來的stream流將會把這兩個特性結合在一起,讓我們體會到其編寫代碼的簡潔之處。
正如前面所講到的lambda表達式體現的是一種**函數式編程思想**,更加關注于**做什么**,而不是**怎么做**。而stream流是一種流式操作, 和lambda表達式一起可以更加關注于**做什么**本身。
stream流和IO流中的流沒有任何關系,是兩個完全不同的概念。stream流的概念可以看成工廠中的流水線。在工廠的流水線生產中,每一步中它都要對源材料進行操作,最后得到想要的產品。stream流也是類似的,每一步它將對源數據進行操作(過濾模式),最后得出符合條件的數據。
因此,stream流主要是用在集合的聚合操作中,使用它可以大大簡化了集合的過濾數據的操作。就跟我們使用SQL語句查詢數據中的數據一樣,我們只是關注了查了哪些數據本身,而底層究竟是怎么查是由數據庫系統幫我們實現好的,stream流也是類似的。
## 集合遍歷操作舉例
### 復雜的操作
集合做為java中最常用的框架,我們在程序中或多或少都要用到它,其中最常見的應該就要屬遍歷操作了。在以前的遍歷集合的操作中,最常見的就是for迭代器循環和增強for循環了。例如下面使用foreach篩選出字符串數組中長度大于4,包含a的數據。
```java
import java.util.ArrayList;
import java.util.List;
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("python");
list.add("java");
list.add("javascript");
List<String> list1 = new ArrayList<>(); //創建一個新列表,保存長度大于4的數據
for (String str : list) {
if (str.length() > 4) {
list1.add(str);
}
} // list1=["python", "javascript"]
List<String> list2 = new ArrayList<>(); //創建一個新列表,保存含有a的數據
for(String str : list1) {
if (str.contains("a")) {
list2.add(str);
}
}
for (String str : list2) { //最后遍歷數據
System.out.println(str);
} // javascript
}
}
```
從上面的例子可以看出,為了達到想要的操作,竟然使用了3個循環,并且還額外增加了2個臨時的列表來保存數據,3個for循環也有點冗余了,這里這么寫只是為了體現出每步的步驟,與stream流的方式進行對比。
其實我們想一想就可以發現,for循環這個語法好像不是必要的,它更像一個工具,告訴我們怎么做,但是其實我們想要僅僅只是做**取出長度大于4并且含有a的數據這件事罷了**。下面我們來看stream流API是怎么關注于“做什么”本身的。
### 簡潔的操作
```java
import java.util.ArrayList;
import java.util.List;
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("python");
list.add("java");
list.add("javascript");
list.stream()
.filter(s -> s.length() > 4) //過濾長度大于4的數據
.filter(s -> s.contains("a")) //過濾有a的數據
.forEach(s -> System.out.println(s)); //打印數據,這里更加推薦的是使用方法引用的方式 System.out::println
}
}
```
可以發現,用API操作很直接明了,filter用來過濾數據,forEach用來遍歷數據,而不用在寫for循環和if條件的判斷了。至于里面究竟是怎么實現的,在應用層面上,我們并不用太過于關心。
## stream流思想
stream流不是任何一種數據類型,它并不會存儲數據,本身也不是數據(與IO流沒關系),它更像是一種函數模型,在這個模型方案中體現了該函數對數據做了哪些操作。如圖:
:-: 
圖中通過filter,map,skip等操作(后面會講這些操作的具體使用方式)將集合數據進行一步步的操作,值得注意的是,這些操作并沒有被真正的執行,只有在最后一步進行count操作的時候,才會真正的執行前面的函數對集合進行操作,并且它是不會操作到集合本身的內容的。這種特性得益于lambda的延遲執行特性。例如上面的例子再遍歷一次list集合:
:-: 
### 相關概念
流的基礎特征:
- Pipelining:管道,即每一次中間執行的操作都會返回一個流對象,例如filter方法返回值仍然是一個流對像,這樣就可以在每個方法之后形成鏈式調用,就像管道一樣連接起來。
- 內部迭代:以前使用for循環時進行的是在集合外部進行迭代的(程序由我們自己編寫),這稱為外部迭代,而stream是通過自身提供的內部迭代方法進行數據的遍歷(迭代方式不可見)
- 元素類型單一:stream流中的元素類型的是單一的,不能操作例如map這種鍵值對的元素。
常用方法分類:
- **延遲方法**:指的是這類方法會返回一個stream流對象,例如filter,map。因為返回的是一個stream流對象,因此這些方法支持鏈式調用,但是就跟前面講到的那樣,這些方法并不會真正執行,只是提供了一個調用鏈模型。只有當執行終結方法時這些方法才會真正的被執行
- **終結方法**:指的是這類方法的返回值不再是stream流對象,因此不能在使用鏈式調用的方法繼續調用下去,管道連接將在這里停止,一旦調用這類方法,前面的函數模型也會真正的被執行。這類方法有**count**和**forEach**。同時,一旦調用了這類方法,之后在調用該stream流對象的方法就會報錯。
## 獲取stream流對象的方式
- 通過Collection集合對象獲取。
- Stream接口中有個靜態方法of可以獲取數組對應的流對象。
### Collection集合對象獲取
在Collection接口中加入了默認的stream()方法用來獲取流對象,因此其實現類都能通過該方法獲取流對象
```java
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
```
例如:
```java
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Vector<String> vector = new Vector<>();
Stream<String> stream3 = vector.stream();
//...Collection實現類及其子類均可獲得
}
}
```
但是由于Map集合并不是Collection接口的實現類,因此不能通過調用stream()方法來獲取流對象,這里是因為Map集合是鍵值對的形式存在的,不能符合流元素的單一特征。但是可以獲取其鍵,值對應的流對象。例如
```java
public class StreamDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream(); //獲取鍵的流對象
Stream<Integer> valueStream = map.values().stream(); //獲取值的流對象
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); //獲取Entry類型的流對象
}
}
```
### 靜態方法獲取
stream接口中有兩個of靜態方法,用來獲取單個數組的流對象或者數組的流對象
```java
public static<T> Stream<T> of(T t) {
return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
public static<T> Stream<T> of(T... values) { //可變參數,也可以傳遞一個數組
return Arrays.stream(values);
}
```
例如:
```java
public class StreamMapDemo {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("6", "8", "10"); //傳遞不固定的參數
Stream<String> stream2 = Stream.of("6");
}
}
```
## 常用方法概述
### filter
```java
Stream<T> filter(Predicate<? super T> predicate);
```
使用Predicate函數式接口中的test方法來過濾數據,test方法用來判斷參數T是否判斷條件,符合條件的話返回true,否則返回false,在這里配合filter使用的話即如果test返回false,則過濾該數據。例如上面使用的:
```java
list.stream().filter(s -> s.length() > 4);
```
即當元素的長度大于4時,Predicate接口的test方法返回true,則不用過濾該數據。是一個**延遲方法**。
### map
```java
Stream<R> map(Function<? super T, ? extends R> mapper);
```
使用Function函數式接口的apply方法,將T類型轉化R類型數據返回,即將T映射成R。例如,將字符串數字數組轉化為Integer數組
```java
public class StreamMapDemo {
public static void main(String[] args) {
Stream<String> original = Stream.of("6", "8", "10");
Stream<Integer> result = original.map(str‐>Integer.parseInt(str)); //String 類型轉化為Integer類型
}
}
```
是一個**延遲方法**。
### limit
```java
Stream<T> limit(long maxSize);
```
用來截取流元素的前maxSize個數據,如果maxSize大于集合的長度,則不用截取。是一個**延遲方法**。
### skip
```java
Stream<T> skip(long n);
```
與limit相反的是,skip是用來跳過前面的n個元素,如果n大于流元素集合的長度,則會得到一個元素為0的空流。是一個**延遲方法**。
### concat
```java
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {}
```
如果希望合并兩個流對象的話,可以使用Stream接口的靜態方法concat,用來將兩個流a,b 合并成一個新的stream流并返回。
### forEach
```java
void forEach(Consumer<? super T> action);
```
由方法定義可以方法,該方法使用了Consumer接口,該接口用來消費數據,對數據進行處理的,用來對流的每個元素執行此操作。是一個**終結方法**。
### count
```java
long count();
```
與Collection集合中的size一樣,用于統計stream元素的個數(當然這里stream流并不是容器);是一個終結方法。
> 備注:其實stream流中的方法究竟是延遲方法還是終結方法,主要看其返回值是否是一個流對象即可,是的話就是延遲方法,否則就是終結方法。
## 小結
1. stream流概念并不同于IO流,它是lambda表達式函數式編程思想的衍生物。
2. 使用stream流可以大大簡化我們對Collection集合的操作,并且Collection集合提供了獲取該對象的stream的方法。
3. stream流的延遲方法(可以看返回值區分)并不會真正執行,只有到調用終結方法時才會將該stream流對象按照所連接的管道順序執行。
- 第一章 Java基礎
- ThreadLocal
- Java異常體系
- Java集合框架
- List接口及其實現類
- Queue接口及其實現類
- Set接口及其實現類
- Map接口及其實現類
- JDK1.8新特性
- Lambda表達式
- 常用函數式接口
- stream流
- 面試
- 第二章 Java虛擬機
- 第一節、運行時數據區
- 第二節、垃圾回收
- 第三節、類加載機制
- 第四節、類文件與字節碼指令
- 第五節、語法糖
- 第六節、運行期優化
- 面試常見問題
- 第三章 并發編程
- 第一節、Java中的線程
- 第二節、Java中的鎖
- 第三節、線程池
- 第四節、并發工具類
- AQS
- 第四章 網絡編程
- WebSocket協議
- Netty
- Netty入門
- Netty-自定義協議
- 面試題
- IO
- 網絡IO模型
- 第五章 操作系統
- IO
- 文件系統的相關概念
- Java幾種文件讀寫方式性能對比
- Socket
- 內存管理
- 進程、線程、協程
- IO模型的演化過程
- 第六章 計算機網絡
- 第七章 消息隊列
- RabbitMQ
- 第八章 開發框架
- Spring
- Spring事務
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 數據庫
- Mysql
- Mysql中的索引
- Mysql中的鎖
- 面試常見問題
- Mysql中的日志
- InnoDB存儲引擎
- 事務
- Redis
- redis的數據類型
- redis數據結構
- Redis主從復制
- 哨兵模式
- 面試題
- Spring Boot整合Lettuce+Redisson實現布隆過濾器
- 集群
- Redis網絡IO模型
- 第十章 設計模式
- 設計模式-七大原則
- 設計模式-單例模式
- 設計模式-備忘錄模式
- 設計模式-原型模式
- 設計模式-責任鏈模式
- 設計模式-過濾模式
- 設計模式-觀察者模式
- 設計模式-工廠方法模式
- 設計模式-抽象工廠模式
- 設計模式-代理模式
- 第十一章 后端開發常用工具、庫
- Docker
- Docker安裝Mysql
- 第十二章 中間件
- ZooKeeper