一. 簡介
Stream(流)是Java 8 提供的高效操作集合類(Collection)數(shù)據(jù)的API。
優(yōu)點(diǎn):
- 借助 Lambda 表達(dá)式,提高效率和可讀性。
- 集成各種操作,極大的提高效率和代碼維護(hù)。
- 提供串行和并行處理。
- 無需擔(dān)心時(shí)間復(fù)雜度問題,因?yàn)榱魇窍劝阉胁僮骷系揭黄?,然后再對?shù)據(jù)遍歷處理。
二. 相關(guān)用法
使用Stream 的基本步驟,
- 創(chuàng)建Stream
- 轉(zhuǎn)換Stream,每次轉(zhuǎn)換原有Stream對象不改變,返回一個(gè)新的Stream對象(可以有多次轉(zhuǎn)換)
- 對Stream 進(jìn)行聚合(Reduce)操作,獲取想要的結(jié)果
1. 創(chuàng)建Stream
創(chuàng)建Stream 有兩種方式,一種是通過Stream接口的靜態(tài)工廠方法(Java8里接口可以帶靜態(tài)方法);另一種是通過Collection接口的默認(rèn)方法(默認(rèn)方法:Default method,也是Java8中的一個(gè)新特性,就是接口中的一個(gè)帶有實(shí)現(xiàn)的方法)–stream(),把一個(gè)Collection對象轉(zhuǎn)換成Stream。
1)使用靜態(tài)方法創(chuàng)建Stream
// 1\. 通過of 方法
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
// 2\. 通過generate 方法創(chuàng)建無窮stream
Stream stream1 = Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
});
// 上面的可以寫成lambda 表達(dá)式
Stream stream1 = Stream.generate(() -> Math.random());
// 或方法引用
Stream stream1 = Stream.generate(Math::random);
上例通過generate 方法是生成一個(gè)無限長度的Stream,其中值是隨機(jī)的。這個(gè)無限長度Stream 是懶加載的,一般這種無限長度的Stream都會(huì)配合Stream的limit()方法來用。
iterate方法:也是生成無限長度的Stream,和generator不同的是,其元素的生成是重復(fù)對給定的種子值(seed)調(diào)用用戶指定函數(shù)來生成的。其中包含的元素可以認(rèn)為是:seed,f(seed),f(f(seed)) 無限循環(huán),例:
Stream.iterate(1, it -> it + 2).limit(6).forEach(System.out::println);
打印一個(gè)從1 開始的公差為2 的等差數(shù)列的前6 項(xiàng),注意,如果不調(diào)用limit 方法將一直打印下去。
2)調(diào)用Collection 的子類的stream() 方法
List<String> strings = Arrays.asList("a", "ab", "abc", "abcd", "abcde");
Stream stream = strings.stream();
2. Stream 的操作
1)操作類型
Intermediate:中間操作,一個(gè)流可以后面跟隨零個(gè)或多個(gè) intermediate 操作。其目的主要是打開流,做出某種程度的數(shù)據(jù)映射/過濾,然后返回一個(gè)新的流,交給下一個(gè)操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調(diào)用到這類方法,并沒有真正開始流的遍歷。
包括:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unorderedTerminal:終止操作,一個(gè)流只能有一個(gè) terminal 操作,當(dāng)這個(gè)操作執(zhí)行后,流就被使用“光”了,無法再被操作。所以這必定是流的最后一個(gè)操作。Terminal 操作的執(zhí)行,才會(huì)真正開始流的遍歷,并且會(huì)生成一個(gè)結(jié)果,或者一個(gè) side effect。
包括:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iteratorShort-circuiting: 對于一個(gè) intermediate 操作,如果它接受的是一個(gè)無限大(infinite/unbounded)的 Stream,但返回一個(gè)有限的新 Stream。 對于一個(gè) terminal 操作,如果它接受的是一個(gè)無限大的 Stream,但能在有限的時(shí)間計(jì)算出結(jié)果。 當(dāng)操作一個(gè)無限大的 Stream,而又希望在有限時(shí)間內(nèi)完成操作,則在管道內(nèi)擁有一個(gè) short-circuiting 操作是必要非充分條件。
包括:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
2)轉(zhuǎn)換流的操作
distinct : 對于Stream中包含的元素進(jìn)行去重操作(去重邏輯依賴元素的equals方法),新生成的Stream中沒有重復(fù)的元素;
List<String> strings = Arrays.asList("a", "ab", "ab", "abc", "abcd", "abcde");
strings.stream().distinct().forEach(System.out::println);
上例代碼會(huì)將重復(fù)的“ab” 去除。
filter :對于Stream中包含的元素使用給定的過濾函數(shù)進(jìn)行過濾操作,新生成的Stream只包含符合條件的元素;
List<String> strings = Arrays.asList("a", "ab", "ab", "abc", "abcd", "abcde");
// 篩選包含“c” 的字符串
strings.stream().filter(it -> it.contains("c")).forEach(System.out::println);
map : 對于Stream中包含的元素使用給定的轉(zhuǎn)換函數(shù)進(jìn)行轉(zhuǎn)換操作,新生成的Stream只包含轉(zhuǎn)換生成的元素。這個(gè)方法有三個(gè)對于原始類型的變種方法,分別是:mapToInt,mapToLong和mapToDouble。這三個(gè)方法也比較好理解,比如mapToInt就是把原始Stream轉(zhuǎn)換成一個(gè)新的Stream,這個(gè)新生成的Stream中的元素都是int類型。之所以會(huì)有這樣三個(gè)變種方法,可以免除自動(dòng)裝箱/拆箱的額外消耗;
List<String> strings = Arrays.asList("a", "ab", "ab", "abc", "abcd", "abcde");
// 將字符串中的字母 “a” 替換成 “x”
strings.stream().map(it -> it.replace('a', 'x')).forEach(System.out::println);
flatMap :map 生成的是個(gè) 1:1 映射,每個(gè)輸入元素,都按照規(guī)則轉(zhuǎn)換成為另外一個(gè)元素。還有一些場景,是一對多映射關(guān)系的,這時(shí)需要 flatMap。
List<List<String>> lists = Arrays.asList(
Arrays.asList("ab", "abc", "abcd"),
Arrays.asList("xy", "xyz"),
Arrays.asList("6")
);
// 輸出 三個(gè)Stream 對象
lists.stream().map(it -> it.stream().filter(s -> !s.equals("6"))).forEach(System.out::println);
// 輸出 字符串
lists.stream().flatMap(str -> str.stream().filter(s -> !s.equals("6"))).forEach(System.out::println);
flatMap 把 輸入 Stream 中的層級(jí)結(jié)構(gòu)扁平化,就是將最底層元素抽出來放到一起,最終 輸出 的新 Stream 里面已經(jīng)沒有 List 了,都是直接的字符串。
peek :生成一個(gè)包含原Stream的所有元素的新Stream,同時(shí)會(huì)提供一個(gè)消費(fèi)函數(shù)(Consumer實(shí)例),新Stream每個(gè)元素被消費(fèi)的時(shí)候都會(huì)執(zhí)行給定的消費(fèi)函數(shù),可用于調(diào)試,在轉(zhuǎn)換流的過程中輸出流中元素的值,如:
List<String> ss = strings.stream().filter(str -> str.length() > 2).peek(System.out::println)
.map(str -> str.replace('a', 'x'))
.peek(System.out::println)
.collect(Collectors.toList());
上例可以觀察在map 操作前后每個(gè)元素的輸出。
limit :對一個(gè)Stream進(jìn)行截?cái)嗖僮?,獲取其前N個(gè)元素,如果原Stream中包含的元素個(gè)數(shù)小于N,那就獲取其所有的元素
List<String> strings = Arrays.asList("a", "ab", "ab", "abc", "abcd", "abcde");
// 輸出前 3 個(gè)元素
strings.stream().limit(3).forEach(System.out::println);
skip :返回一個(gè)丟棄原Stream的前N個(gè)元素后剩下元素組成的新Stream,如果原Stream中包含的元素個(gè)數(shù)小于N,那么返回空Stream
List<String> strings = Arrays.asList("a", "ab", "ab", "abc", "abcd", "abcde");
// 跳過前 3 個(gè)元素,輸出后面的元素
strings.stream().skip(3).forEach(System.out::println);
sorted :對流內(nèi)的元素進(jìn)行排序,sorted 方法有兩種重載,第一種是無參,此時(shí)要求元素需要實(shí)現(xiàn)Comparable 接口,第二種是傳一個(gè)Comparator 類型參數(shù)
static class P {
int age;
String name;
P(String name, int age) {
this.name = name;
this.age = age;
}
}
List<P> ps = Arrays.asList(new P("abc", 12), new P("xyz", 23),
new P("bcd", 666), new P("opq", 33), new P("abc", 12));
// 傳入一個(gè)lambda 表達(dá)式作為排序條件
ps.stream().distinct().sorted((o1, o2) -> o1.age - o2.age).forEach(System.out::println);
3)對流取結(jié)果(reduction)
這些操作都是terminal 操作,下面介紹一些比較常用的。
collect :它可以把Stream中的要有元素收集到一個(gè)結(jié)果容器中(比如Collection)。先看一下最通用的collect方法的定義:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
先來看看這三個(gè)參數(shù)的含義:
?Supplier supplier 是一個(gè)工廠函數(shù),用來生成一個(gè)新的容器;
?BiConsumer accumulator 也是一個(gè)函數(shù),用來把Stream中的元素添加到結(jié)果容器中;
?BiConsumer combiner 還是一個(gè)函數(shù),用來把中間狀態(tài)的多個(gè)結(jié)果容器合并成為一個(gè)(并發(fā)的時(shí)候會(huì)用到)。示例
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(() -> new ArrayList<Integer>(), // 創(chuàng)建新的List 實(shí)例
(list, item) -> list.add(item), // 將item 添加到list 中
(list1, list2) -> list1.addAll(list2)); // 把第二個(gè)list 全部加入第一個(gè)中,用于并發(fā)
上面代碼就是將一個(gè)list 過濾掉null 然后收集到一個(gè)新的容器中。
上例看起來代碼很多,很復(fù)雜,collect 方法還有更加簡單的使用方式,定義如下
<R, A> R collect(Collector<? super T, A, R> collector);
同時(shí),Java8還給我們提供了Collector的工具類 – Collectors,其中已經(jīng)定義了一些靜態(tài)工廠方法,比如:Collectors.toCollection() 收集到Collection中, Collectors.toList() 收集到List 中和Collectors.toSet() 收集到Set中,等等。所以上例可簡化成:
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(Collectors.toList());
Collectors.toArray() —— 由stream中的元素得到的數(shù)組,默認(rèn)是Object[],可以通過參數(shù)設(shè)置需要結(jié)果的類型:
String[] words2 = Stream.of("You", "may", "assume").toArray(String[]::new);
Collectors.toMap() —— 將stream中的元素映射為 map 的形式,兩個(gè)參數(shù)分別用于生成對應(yīng)的key和value的值:
List<String> strings = Arrays.asList("abc", "ab", "abcde", "a", "abcd", "ab");
// 把字符串中 a 替換為 x 作為key,在字符串后加一個(gè) “-233” 作為value
Map<String, String> map = strings.stream().collect(Collectors.toMap(str -> str.replace('a', 'x'), str -> str + "-233", (oldStr, newStr) -> newStr));
如果一個(gè)key對應(yīng)多個(gè)value,則會(huì)拋出異常,需要使用第三個(gè)參數(shù)設(shè)置如何處理沖突,比如僅使用原來的value、使用新的value,或者合并。
reduce :這個(gè)方法的主要作用是把 Stream 元素組合起來。它提供一個(gè)起始值(種子),然后依照運(yùn)算規(guī)則(BinaryOperator),和前面 Stream 的第一個(gè)、第二個(gè)、第 n 個(gè)元素組合。從這個(gè)意義上說,字符串拼接、數(shù)值的 sum、min、max、average 都是特殊的 reduce。
reduce 有兩種常用的重載方法,第一個(gè)是接收一個(gè)BinaryOperator 類型參數(shù),BinaryOperator 參數(shù)是一個(gè)執(zhí)行雙目運(yùn)算的 Functional Interface ,假如這個(gè)參數(shù)表示的操作為op,stream中的元素為x, y, z, …,則 reduce() 執(zhí)行的就是 x op y op z ...,所以要求op這個(gè)操作具有結(jié)合性(associative),即滿足: (x op y) op z = x op (y op z)
Optional<T> reduce(BinaryOperator<T> accumulator);
這個(gè)方法返回一個(gè)Optional 對象,這是Java 8 中一個(gè)防止NPE 的類型,之后再介紹。示例:
List<String> strings = Arrays.asList("abc", "ab", "abcde", "a", "abcd", "ab");
Optional<String> op = strings.stream().distinct().filter(str -> str.length() > 1).sorted().reduce((a, b) -> a + " - " + b);
op.ifPresent(System.out::println);
// 輸出 ab - abc - abcd - abcde
第二個(gè)方法接收一個(gè)初始值 和 BinaryOperator 參數(shù):
T reduce(T identity, BinaryOperator<T> accumulator);
和上面方法不同的是,它允許用戶提供一個(gè)循環(huán)計(jì)算的初始值,如果Stream為空,就直接返回該值。而且這個(gè)方法不會(huì)返回Optional,因?yàn)槠洳粫?huì)出現(xiàn)null值。
String ss = strings.stream().distinct().filter(str -> str.length() > 1).sorted().reduce("hhh", (a, b) -> a + " - " + b);
System.out.println(ss);
// 輸出 hhh - ab - abc - abcd - abcde
其他方法:
count :獲取Stream中元素的個(gè)數(shù)。
allMatch:是不是Stream中的所有元素都滿足給定的匹配條件,返回boolean
anyMatch:Stream中是否存在任何一個(gè)元素滿足匹配條件,返回boolean
findFirst : 返回Stream中的第一個(gè)元素,如果Stream為空,返回空Optional
noneMatch:是不是Stream中的所有元素都不滿足給定的匹配條件
max 和 min:使用給定的比較器(Operator),返回Stream中的最大|最小值 的Optional 對象,例
List<String> strings = Arrays.asList("abc", "ab", "abcde", "a", "abcd", "ab");
strings.stream().max(Comparator.comparing(String::length)).ifPresent(System.out::println);
// 輸出 abcde
4)分組(Grouping 和 Partitioning)
groupingBy:表示根據(jù)某一個(gè)字段或條件進(jìn)行分組,返回一個(gè)Map,其中key為分組的字段或條件,value默認(rèn)為list, groupingByConcurrent() 是其并發(fā)版本
List<String> strings = Arrays.asList("abc", "ab", "abcde", "a", "abcd", "ab");
// 根據(jù)字符串長度分組
Map<Integer, List<String>> map = strings.stream().collect(Collectors.groupingBy(String::length));
如果 groupingBy() 分組的依據(jù)是一個(gè)bool條件,則key的值為true/false,此時(shí)與 partitioningBy() 等價(jià),但是 partitioningBy() 的效率更高:
List<String> strings = Arrays.asList("abc", "ab", "abcde", "a", "abcd", "ab");
// 根據(jù)字符串長度是否是偶數(shù),分成兩部分
Map<Boolean, List<String>> map = strings.stream().collect(Collectors.partitioningBy(str -> str.length() % 2 == 0));
groupingBy() 提供第二個(gè)參數(shù),表示 downstream ,即對分組后的value作進(jìn)一步的處理,可以有如下操作
// 分組結(jié)果返回為 Set 而不是List
Map<Integer, Set<String>> map = strings.stream().collect(Collectors.groupingBy(String::length, Collectors.toSet()));
// 返回value 集合中元素個(gè)數(shù)
Map<Integer, Long> map = strings.stream().collect(Collectors.groupingBy(String::length, Collectors.counting()));
// 對value 集合中元素的某個(gè)值求和,同理還有summingDouble,summingLong
Map<Integer, Integer> map = strings.stream().collect(Collectors.groupingBy(String::length, Collectors.summingInt(String::length)));
// 對value 集合中元素求最大值,注意求出的最大值是Optional 類型
Map<Integer, Optional<String>> map = strings.stream().collect(Collectors.groupingBy(String::length, Collectors.maxBy(Comparator.comparing(String::length))));
// 使用mapping對value的字段進(jìn)行map處理
Map<Integer, Set<String>> map = strings.stream().collect(Collectors.groupingBy(String::length, Collectors.mapping(str -> str.replace('a', 'x'), Collectors.toSet())));
// 通過 summarizing[Int|Double|Long] 獲取統(tǒng)計(jì)結(jié)果, 返回一個(gè)value 為 [Int|Double|Long]SummaryStatistics 的實(shí)例,可以調(diào)用該實(shí)例的getAverage 等方法獲得統(tǒng)計(jì)值
Map<Integer, IntSummaryStatistics> map = strings.stream().collect(Collectors.groupingBy(String::length, Collectors.summarizingInt(String::length)));
// 通過reduceing 可以做更多處理,但是并不常用
5)并行操作
Stream 支持并發(fā)操作,前提是需要滿足以下幾點(diǎn)
構(gòu)造一個(gè)paralle stream,默認(rèn)構(gòu)造的 stream 是順序執(zhí)行的,調(diào)用 paralle() 構(gòu)造并行的stream
要執(zhí)行的操作必須是可并行執(zhí)行的,即并行執(zhí)行的結(jié)果和順序執(zhí)行的結(jié)果是一致的,而且必須保證 stream 中執(zhí)行的操作是線程安全的
List<String> sss = strings.stream().parallel().distinct().collect(Collectors.toList());
常見使用場景:
使stream無序,對于 distinct() 和 limit() 等方法,如果不關(guān)心順序,則可以使用并行;
在 groupingBy() 的操作中,map的合并操作是比較重的,可以通過 groupingByConcurrent() 來并行處理,不過前提是parallel stream;
注意:在執(zhí)行stream操作時(shí)不能修改stream對應(yīng)的collection。