Java Streams API

作者: 一字馬胡
轉(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丛肢。用以指:

  1. 對于一個(gè) intermediate 操作,如果它接受的是一個(gè)無限大(infinite/unbounded)的 Stream剿干,但返回一個(gè)有限的新 Stream蜂怎。
  2. 對于一個(gè) terminal 操作,如果它接受的是一個(gè)無限大的 Stream置尔,但能在有限的時(shí)間計(jì)算出結(jié)果杠步。

一般的使用Stream API的流程是:

  1. 創(chuàng)建一個(gè)Stream,可以通過多種方式來創(chuàng)建一個(gè)Stream榜轿,下文中會專門寫到
  2. 對Stream進(jìn)行一系列的Intermediate操作幽歼,但是需要注意的是操作并不會立刻執(zhí)行,而是會等待一個(gè)Terminal類型的操作之后才會執(zhí)行
  3. 進(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碳抄。下面分為幾類:

  1. 從Collection創(chuàng)建Stream
  2. 從數(shù)組創(chuàng)建Stream
  3. 使用Stream提供的IntStream愉老、LongStream、DoubleStream
  4. 使用BufferedReader創(chuàng)建Stream
  5. 使用自己的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接口的所以方法:

Stream API

本文將選擇一些常用的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尿贫,勢必會提高我們的工作效率电媳。

Optional類提供的方法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市庆亡,隨后出現(xiàn)的幾起案子匾乓,更是在濱河造成了極大的恐慌,老刑警劉巖又谋,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拼缝,死亡現(xiàn)場離奇詭異娱局,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咧七,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門衰齐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猪叙,你說我怎么就攤上這事娇斩。” “怎么了穴翩?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵犬第,是天一觀的道長。 經(jīng)常有香客問我芒帕,道長歉嗓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任背蟆,我火速辦了婚禮鉴分,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘带膀。我一直安慰自己志珍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布垛叨。 她就那樣靜靜地躺著伦糯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嗽元。 梳的紋絲不亂的頭發(fā)上敛纲,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機(jī)與錄音剂癌,去河邊找鬼淤翔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛佩谷,可吹牛的內(nèi)容都是我干的旁壮。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼谐檀,長吁一口氣:“原來是場噩夢啊……” “哼抡谐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起稚补,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤童叠,失蹤者是張志新(化名)和其女友劉穎框喳,沒想到半個(gè)月后课幕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厦坛,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年乍惊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杜秸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡润绎,死狀恐怖撬碟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情莉撇,我是刑警寧澤呢蛤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站棍郎,受9級特大地震影響其障,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涂佃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一励翼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辜荠,春花似錦汽抚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狱从,卻和暖如春膨蛮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背季研。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工敞葛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人与涡。 一個(gè)月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓惹谐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親驼卖。 傳聞我的和親對象是個(gè)殘疾皇子氨肌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容