作者: 一字馬胡
轉(zhuǎn)載標(biāo)志 【2017-11-03】
更新日志
日期 | 更新內(nèi)容 | 備注 |
---|---|---|
2017-11-03 | 添加轉(zhuǎn)載標(biāo)志 | 持續(xù)更新 |
Java Stream概述
Java Stream是一系列對集合便利操作的工具集怀喉,可以對各種數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)進(jìn)行聚合操作熏迹,比如對List元素的去重操作,對Set集合的分組操作等等材原,使用Stream API可以很方便的實(shí)現(xiàn)一些我們平常需要些大段代碼來實(shí)現(xiàn)的功能。最為重要的一點(diǎn)是Stream使用了Fork/Join框架對任務(wù)進(jìn)行拆分運(yùn)行季眷,可以非常高效的處理我們的各種操作余蟹。
Stream API是一套專注操作的接口,它不緩存任何數(shù)據(jù)子刮,對數(shù)據(jù)進(jìn)行操作之前都會創(chuàng)建 一個(gè)新的Stream然后再操作威酒,也就是說不管進(jìn)行了多少個(gè)Stream操作窑睁,數(shù)據(jù)源還是不會變,看下面簡單的例子:
String[] nameArray = {"hujian", "libai", "hujian", "wanganshi"};
Stream.of(nameArray)
.parallel()
.distinct()
.map(String::toUpperCase)
.collect(Collectors.toCollection(ArrayList::new))
.forEach(System.out::println);
for(int i = 0;i < nameArray.length; i ++) {
System.out.println("->" + nameArray[i]);
}
//output
HUJIAN
LIBAI
WANGANSHI
->hujian
->libai
->hujian
->wanganshi
可以發(fā)現(xiàn)葵孤,盡管我們對源頭數(shù)組施加了很多操作担钮,但是最后數(shù)組內(nèi)容都沒有變化,這也就說明了每一個(gè)Stream操作都會新建一個(gè)Stream不會破壞源數(shù)據(jù)的完整性尤仍。
Stream API中的操作分為兩類:
Intermediate:一個(gè)流可以后面跟隨零個(gè)或多個(gè) intermediate 操作箫津。其目的主要是打開流,做出某種程度的數(shù)據(jù)操作宰啦,然后返回一個(gè)新的流苏遥,交給下一個(gè)操作使用。這類操作都是惰性化的(lazy)绑莺,就是說暖眼,僅僅調(diào)用到這類方法,并沒有真正開始流的遍歷纺裁。
Terminal:一個(gè)流只能有一個(gè) terminal 操作诫肠,當(dāng)這個(gè)操作執(zhí)行后,流就被使用“光”了欺缘,無法再被操作栋豫。所以這必定是流的最后一個(gè)操作。Terminal 操作的執(zhí)行谚殊,才會真正開始流的遍歷丧鸯,并且會生成一個(gè)結(jié)果,或者一個(gè) side effect嫩絮。
還有一種操作被稱為 short-circuiting丛肢。用以指:
- 對于一個(gè) intermediate 操作,如果它接受的是一個(gè)無限大(infinite/unbounded)的 Stream剿干,但返回一個(gè)有限的新 Stream蜂怎。
- 對于一個(gè) terminal 操作,如果它接受的是一個(gè)無限大的 Stream置尔,但能在有限的時(shí)間計(jì)算出結(jié)果杠步。
一般的使用Stream API的流程是:
- 創(chuàng)建一個(gè)Stream,可以通過多種方式來創(chuàng)建一個(gè)Stream榜轿,下文中會專門寫到
- 對Stream進(jìn)行一系列的Intermediate操作幽歼,但是需要注意的是操作并不會立刻執(zhí)行,而是會等待一個(gè)Terminal類型的操作之后才會執(zhí)行
- 進(jìn)行一個(gè)Terminal操作谬盐,使得所有施加在Stream的操作生效甸私。
我們會對Stream API的執(zhí)行有一個(gè)誤區(qū),比如上面我們的代碼飞傀,首先施加了去重的操作颠蕴,接著使用map進(jìn)行大寫轉(zhuǎn)換泣刹,然后將Stream變化為一個(gè)ArrayList,最后輸出List的元素內(nèi)容犀被。進(jìn)行了四個(gè)操作椅您,那么Stream API會進(jìn)行四重循環(huán)來做完這套操作呢?顯然是不會這樣做的寡键,Stream API記錄了所有需要在Stream上執(zhí)行的操作掀泳,然后在遇到terminal操作的時(shí)候,會對同一個(gè)Stream的元素進(jìn)行整套操作西轩,所以循環(huán)的應(yīng)該是操作集合员舵,而不是對元素進(jìn)行循環(huán)執(zhí)行。比如上面的四個(gè)操作藕畔,會對每一個(gè)元素一次執(zhí)行四個(gè)操作马僻,使人迷惑的是去重如何操作呢?Stream API使用了HashSet來操作注服,Set當(dāng)然可以去重韭邓,我們只需要對每一個(gè)元素都add到HashSet中去,就達(dá)到去重的目的了溶弟。對于一些那一理解的操作女淑,我們應(yīng)該去閱讀源碼,雖然有些時(shí)候看不懂源碼辜御,因?yàn)樵创a過于龐大和復(fù)雜鸭你,但是可以抓住重點(diǎn),尋找一些我們希望看到的信息擒权,大概明白流程也就可以了袱巨。
如何創(chuàng)建Stream
使用Stream API的第一步當(dāng)然是創(chuàng)建一個(gè)Stream,有多種方法來創(chuàng)建Stream碳抄。下面分為幾類:
- 從Collection創(chuàng)建Stream
- 從數(shù)組創(chuàng)建Stream
- 使用Stream提供的IntStream愉老、LongStream、DoubleStream
- 使用BufferedReader創(chuàng)建Stream
- 使用自己的Supplier來創(chuàng)建Stream
下面的代碼展示了這些這些創(chuàng)建方法的使用方法:
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
/**
* Created by hujian06 on 2017/9/28.
*
*
*/
public class StreamDemo {
/*some type of creating Stream<?>*/
enum TYPE {
FROM_COLLECTION,
FROM_ARRAY_0,
FROM_ARRAY_1,
FROM_BUFFERED_READER,
INT_STREAM,
LONG_STREAM,
DOUBLE_STREAM,
FROM_FILE_WALK,
FROM_SUPPLIER
}
/**
* Creating a Stream
* @param type the type
* @param collection collection
* @param array array
* @param bufferedReader bufferedReader
* @param <T> type
* @return the Stream
*/
@SuppressWarnings(value = "unchecked")
private static <T> Stream<T> create(TYPE type, Collection<?> collection, T[] array,
BufferedReader bufferedReader, Supplier<T> supplier)
throws Exception {
Stream<T> stream = null;
switch (type) {
case FROM_COLLECTION:
if (collection == null) throw new Exception("NPE of Collection");
stream = (Stream<T>) collection.stream();
break;
case FROM_ARRAY_0:
if (array == null) throw new Exception("NPE of Array");
stream = Arrays.stream(array);
break;
case FROM_ARRAY_1:
if (array == null) throw new Exception("NPE of Array");
stream = Stream.of(array);
break;
case FROM_BUFFERED_READER:
if (bufferedReader == null) throw new Exception("NPE of bufferedReader");
stream = (Stream<T>) bufferedReader.lines();
break;
case INT_STREAM:
//stream = (Stream<T>) IntStream.range(0, 1000); //mock
break;
case LONG_STREAM:
//stream = (Stream<T>) LongStream.range(0, 100000);//mock
break;
case DOUBLE_STREAM:
//stream = (Stream<T>) DoubleStream.of(3.1415926); //mock
break;
case FROM_FILE_WALK:
break;
case FROM_SUPPLIER:
if (supplier == null) throw new Exception("NPE of supplier");
stream = Stream.generate(supplier);
break;
}
return stream;
}
//loop to generate.
private static Supplier<Integer> listSupplier = () -> {
Random random = new Random(47);
return random.nextInt();
};
public static void main(String ... args) throws Exception {
String[] nameArray = {"hujian", "libai", "hujian"};
List<String> nameList = new ArrayList<>();
Stream<String> stringStream = create(TYPE.FROM_ARRAY_0, null, nameArray, null, null);
nameList = stringStream.collect(Collectors.toCollection(ArrayList::new));
Stream<String> stringStream1 = create(TYPE.FROM_COLLECTION, nameList, null, null, null);
//stringStream.distinct().forEach(System.out::println);
stringStream1.forEach(System.out::println);
Stream<Integer> integerStream = create(TYPE.FROM_SUPPLIER, null, null, null, listSupplier);
integerStream.forEach(System.out::println);
}
}
關(guān)于更加具體和更多創(chuàng)建Stream的方式纳鼎,可以查閱更多的資料或者直接閱讀官方文檔來學(xué)習(xí)。
Stream操作
上文中介紹了如何創(chuàng)建Stream的一些方法裳凸,本節(jié)將學(xué)習(xí)Stream提供的操作API贱鄙。下面的圖片展示了Stream接口的所以方法:
本文將選擇一些常用的API來說明,不會涉及所有的API姨谷。
- Intermediate類型的操作
api | description | function | code |
---|---|---|---|
filter | Returns a stream consisting of the elements of this stream that match the given predicate. | 過濾器逗宁,將不需要的內(nèi)容過濾掉,只留下我們想要的數(shù)據(jù) | |
map | Returns a stream consisting of the results of applying the given function to the elements of this stream | 將數(shù)據(jù)轉(zhuǎn)換為另外一種格式的操作梦湘,比如我們可以將String類型的數(shù)據(jù)轉(zhuǎn)換為全是大寫字母的String | |
mapToInt | Returns an {@code IntStream} consisting of the results of applying the given function to the elements of this stream. | 屬于map瞎颗,但是更為具體件甥,指定了將要轉(zhuǎn)換達(dá)到的類型設(shè)定為int,同類型的還有mapToLong,maptoDouble哼拔,下面不再敘述 | integerStream.mapToInt(value -> value + 100); |
flatMap | Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Each mapped stream is {@link java.util.stream.BaseStream#close() closed} after its contents have been placed into this stream. (If a mapped stream is {@code null} an empty stream is used, instead.) | map操作達(dá)到的效果是一對一的引有,也就是只會生成一個(gè)轉(zhuǎn)換后的元素,而flatMap則是一對多的倦逐,使得對每一個(gè)上游元素譬正,你都可以生成一個(gè)新的Stream,這種處理模式將底層數(shù)據(jù)扁平化檬姥,后面的代碼展示了如何使用flatMap | integerStream.flatMap((Function<Integer, Stream<?>>) integer -> Stream.of(integer)); |
flatMapToLong | * | 更加具體的指定了需要轉(zhuǎn)換成的數(shù)據(jù)類型曾我,此操作希望將上游的每一個(gè)元素都轉(zhuǎn)換為一個(gè)LongStream,榆次類似的還要flatMapToInt健民、flatMapToDoble抒巢,下文中不再敘述 | integerStream.flatMapToLong(integer -> LongStream.of(integer)); |
distinct | * | 這是比較常用的操作,可以對元素類型去重秉犹,Stream使用HashSet來實(shí)現(xiàn)去重 | |
sorted | * | 排序蛉谜,使用自然排序 | Stream.of(arrays).sorted().forEach(System.out::println); |
sorted(Com[arator) | * | 帶參數(shù)的排序操作,參數(shù)由你指定排序的規(guī)則 | |
limit | Returns a stream consisting of the elements of this stream, truncated to be no longer than {@code maxSize} in length. | 用于限流凤优,Stream最多只會取limit設(shè)定的元素個(gè)數(shù)到下游 | Stream.of(1,2,3,4,5).limit(2).forEach(System.out::println); |
skip | * | 和limit類似悦陋,但是skip是跳過前幾個(gè)元素,然后再將接下來的元素傳送到下游操作 | Stream.of(1,2,3,4,5).skip(2).forEach(System.out::println); |
- Terminal類型的操作
api | description | function | code |
---|---|---|---|
forEach | * | 用于遍歷元素 | |
forEachOrder | * | forEach使用并行筑辨,而forEachOrder使用串行 | |
toArray | * | 轉(zhuǎn)換為array | |
reduce | * | 組合元素俺驶,提供一個(gè)起始值,然后從這個(gè)值迭代棍辕,每個(gè)元素都將參與設(shè)定的運(yùn)算暮现,適合對元素求和、求最大最小值等操作 | int sum = Stream.of(1,2,3).reduce(0, (integer, integer2) -> integer + integer2); |
collect | * | 將Stream轉(zhuǎn)換為一個(gè)Collection | Stream.of(1,2,3,4,5).collect(Collectors.toCollection(ArrayList::new)).forEach(System.out::println); |
min/max/count | * | * | * |
- short-circuiting類型的操作
api | description | function | code |
---|---|---|---|
anyMatch | 任意元素滿足即可 | Stream.of(1,2,3,4,5).anyMatch(integer -> integer % 2 == 0); | |
allMatch | 所有元素都需要滿足 | Stream.of(1, 2, 3).allMatch(integer -> integer > 2); | |
noneMatch | 所有元素都不滿足 | Stream.of(1,2,3).noneMatch(integer -> integer % 2 == 0); | |
findFirst | 返回Stream的第一個(gè)元素或者空 | ||
findAny | 返回任何一個(gè)元素然后就結(jié)束了 | ||
limit | 上文已經(jīng)說過 |
擴(kuò)展
值得注意的是楚昭,Optional類也實(shí)現(xiàn)了一些類似Stream API的操作栖袋。下面的圖片展示了Optional類提供的一些操作,在項(xiàng)目中抚太,應(yīng)該盡量使用Optional類來避免null塘幅,而且Optional也提供了強(qiáng)大的數(shù)據(jù)處理能力,結(jié)合Optional和Stream尿贫,勢必會提高我們的工作效率电媳。