Stream流簡介
說到Stream便容易想到I/O 流,而實際上,誰規定“流”就一定是“IO流”呢?在Java 8中,得益於Lambda所帶來的函數式編程,引入了一個全新的Stream概念,用於解決已有集合類庫既有的弊端。
集合的弊端:
每一次篩選都需要定義一個新的集合來存儲結果
每一次操作集合中所有的元素都需要通過循環來遍歷
1.s流式思想概念
Stream流主要用於解決已有集合類庫既有的弊端
整體來看,流式思想類似於工廠車間的“生產流水線”。
當需要對多個元素進行操作(特別是多步操作)的時候,考慮到性能及便利性,我們應該首先拼好一個“模型”步驟方案,然後再按照方案去執行它。
這張圖中展示了過濾、映射、跳過、計數等多步操作,這是一種集合元素的處理方案,而方案就是一種“函數模型”。圖中的每一個方框都是一個“流”,調用指定的方法,可以從一個流模型轉換爲另一個流模型。而最右側的數字3是最終結果。
Stream(流)是一個來自數據源的元素隊列並支持聚合操作
-
元素是特定類型的對象,形成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算。
-
數據源 流的來源。 可以是集合,數組,I/O channel, 產生器generator 等。
-
聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作還有兩個基礎的特徵:
-
Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
-
內部迭代: 以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫做外部迭代。 Stream提供了內部迭代的方式, 通過訪問者模式(Visitor)實現。
2.Stream特點
- Stream流一定得搭建好模型,才能執行(也就是說一定要有終結的方法,纔會執行)。
- Stream流是單向的,不能重複使用。
- Stream流是有延遲性的,每一個操作延遲方法之後會得到一個新的流,除了終結的方法。
- Stream流不能存儲數據。
- Stream流不會更改數據源。
3.使用stream流
Stream流的使用步驟: 獲取流—>調用延遲方法—>調用終結方法
3.1獲取流
java.util.stream.Stream<T>
是Java 8新加入的最常用的流接口。(這並不是一個函數式接口。)
獲取一個流非常簡單,有以下幾種常用的方式:
獲取一個流非常簡單,有以下幾種常用的方式:
- 所有的
Collection
集合都可以通過stream
默認方法獲取流; Stream
接口的靜態方法of
可以獲取數組對應的流。
根據Collection獲取流
首先,java.util.Collection
接口中加入了default方法stream
用來獲取流,所以其所有實現類均可獲取流。
代碼演示:
import java.util.*;
import java.util.stream.Stream;
public class Demo04GetStream {
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();
}
}
根據Map獲取流
java.util.Map
接口不是Collection
的子接口,且其K-V數據結構不符合流元素的單一特徵,所以獲取對應的流需要分key、value或entry等情況:
代碼演示:
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class Demo05GetStream {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
// 通過map集合的keyset()方法獲取鍵的set集合進而獲取流
Stream<String> keyStream = map.keySet().stream();
//通過map集合的values()方法獲取值的set集合進而獲取流
Stream<String> valueStream = map.values().stream();
//通過map集合的entrySet()方法獲取的entry鍵值對對象的set集合進而獲取流
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}
根據數組獲取流
如果使用的不是集合或映射而是數組,由於數組對象不可能添加默認方法,所以Stream
接口中提供了靜態方法of
,使用很簡單:
代碼演示:
import java.util.stream.Stream;
public class Demo06GetStream {
public static void main(String[] args) {
String[] array = { "鋼鐵俠", "綠巨人", "美國隊長", "雷神" };
Stream<String> stream = Stream.of(array);
}
}
備註: of
方法的參數其實是一個可變參數,所以支持數組。
4.Stream流中常用方法
流模型的操作很豐富,這裏介紹一些常用的API。這些方法可以被分成兩種:
-
終結方法:返回值類型不再是
Stream
接口自身類型的方法,因此不再支持類似StringBuilder
那樣的鏈式調用。本小節中,終結方法包括count
和forEach
方法。 -
延遲方法:返回值類型仍然是
Stream
接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其餘方法均爲延遲方法。)備註:本小節之外的更多方法,請自行參考API文檔。
4.1.過濾:filter
可以通過filter
方法將一個流轉換成另一個子集流。方法簽名:
Stream<T> filter(Predicate<? super T> predicate);
該接口接收一個Predicate
函數式接口參數(可以是一個Lambda或方法引用)作爲篩選條件。
4.2.統計個數:count
正如舊集合Collection
當中的size
方法一樣,流提供count
方法來數一數其中的元素個數。
long count();
該方法返回一個long值代表元素個數(不再像舊集合那樣是int值)。
4.3.取用前幾個:limit
limit
方法可以對流進行截取,只取用前n個。方法簽名:
Stream<T> limit(long maxSize);
參數是一個long型,如果集合當前長度大於參數則進行截取;否則不進行操作。
4.4.跳過前幾個:skip
如果希望跳過前幾個元素,可以使用skip
方法獲取一個截取之後的新流:
Stream<T> skip(long n);
如果流的當前長度大於n,則跳過前n個;否則將會得到一個長度爲0的空流。
4.5.映射:map
如果需要將流中的元素映射到另一個流中,可以使用map
方法。方法簽名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
該接口需要一個Function
函數式接口參數,可以將當前流中的T類型數據轉換爲另一種R類型的流。
4.6.組合:concat
如果有兩個流,希望合併成爲一個流,那麼可以使用Stream
接口的靜態方法concat
:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
備註:這是一個靜態方法,與
java.lang.String
當中的concat
方法是不同的。
4.7.逐一處理:forEach
雖然方法名字叫forEach
,但是與for循環中的“for-each”暱稱不同,該方法並不保證元素的逐一消費動作在流中是被有序執行的。
void forEach(Consumer<? super T> action);
該方法接收一個Consumer
接口函數,會將每一個流元素交給該函數進行處理
5.集合元素處理對比
需求:
現在有兩個ArrayList
集合存儲隊伍當中的多個成員姓名,要求使用傳統的for循環(或增強for循環)依次進行以下若干操作步驟:
- 第一個隊伍只要名字爲3個字的成員姓名;
- 第一個隊伍篩選之後只要前3個人;
- 第二個隊伍只要姓張的成員姓名;
- 第二個隊伍篩選之後不要前2個人;
- 將兩個隊伍合併爲一個隊伍;
- 根據姓名創建
Person
對象; - 打印整個隊伍的Person對象信息。
兩個隊伍(集合)的代碼如下:
public class DemoArrayListNames {
public static void main(String[] args) {
//第一支隊伍
ArrayList<String> one = new ArrayList<>();
one.add("迪麗熱巴");
one.add("宋遠橋");
one.add("蘇星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("莊子");
one.add("洪七公");
//第二支隊伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("張無忌");
two.add("趙麗穎");
two.add("張三丰");
two.add("尼古拉斯趙四");
two.add("張天愛");
two.add("張二狗");
// ....
}
}
Person
類的代碼爲:
public class Person {
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
5.1傳統方法處理
使用傳統for循環,代碼如下 :
public class DemoArrayListNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
// ...
List<String> two = new ArrayList<>();
// ...
// 第一個隊伍只要名字爲3個字的成員姓名;
List<String> oneA = new ArrayList<>();
for (String name : one) {
if (name.length() == 3) {
oneA.add(name);
}
}
// 第一個隊伍篩選之後只要前3個人;
List<String> oneB = new ArrayList<>();
for (int i = 0; i < 3; i++) {
oneB.add(oneA.get(i));
}
// 第二個隊伍只要姓張的成員姓名;
List<String> twoA = new ArrayList<>();
for (String name : two) {
if (name.startsWith("張")) {
twoA.add(name);
}
}
// 第二個隊伍篩選之後不要前2個人;
List<String> twoB = new ArrayList<>();
for (int i = 2; i < twoA.size(); i++) {
twoB.add(twoA.get(i));
}
// 將兩個隊伍合併爲一個隊伍;
List<String> totalNames = new ArrayList<>();
totalNames.addAll(oneB);
totalNames.addAll(twoB);
// 根據姓名創建Person對象;
List<Person> totalPersonList = new ArrayList<>();
for (String name : totalNames) {
totalPersonList.add(new Person(name));
}
// 打印整個隊伍的Person對象信息。
for (Person person : totalPersonList) {
System.out.println(person);
}
}
}
運行結果:
Person{name='宋遠橋'}
Person{name='蘇星河'}
Person{name='石破天'}
Person{name='張天愛'}
Person{name='張二狗'}
5.2使用stream流 處理
等效的Stream流式處理代碼爲:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class DemoStreamNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
// ...
List<String> two = new ArrayList<>();
// ...
// 第一個隊伍只要名字爲3個字的成員姓名;
// 第一個隊伍篩選之後只要前3個人;
Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
// 第二個隊伍只要姓張的成員姓名;
// 第二個隊伍篩選之後不要前2個人;
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("張")).skip(2);
// 將兩個隊伍合併爲一個隊伍;
// 根據姓名創建Person對象;
// 打印整個隊伍的Person對象信息。
Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);
}
}
小結:
1. Stream流一定得搭建好模型,才能執行(也就是說一定要有終結的方法,纔會執行)。
2. Stream流是單向的,不能重複使用。
3. Stream流是有延遲性的,每一個操作延遲方法之後會得到一個新的流,除了終結的方法。
4. Stream流不能存儲數據。
5. Stream流不會更改數據源。