Java8之Stream流(五)映射流

Java8之Stream流(一)基礎體驗
Java8之Stream流(二)關(guān)鍵知識點
Java8之Stream流(三)縮減操作
Java8之Stream流(四)并行流
Java8之Stream流(六)收集
Java8之Stream流(七)流與迭代器

經(jīng)過了前面四篇文章的學習,相信大家對Stream流已經(jīng)是相當?shù)氖煜ち怂呷澹瑫r也掌握了一些高級功能了葡缰,如果你之前有閱讀過集合框架的基石Collection接口亏掀,是不是在經(jīng)過前面的學習忱反,以前看不懂的東西,突然之間就恍然大悟了呢滤愕?今天我們的主角是Stream流里面的映射温算。由于之前,映射并沒有再我們的Demo间影,例子中出現(xiàn)過注竿,所以對大家來說可能會稍微有一點點陌生的,但通過這一篇文章魂贬,我相信能解決你的疑問巩割。

在正式開始之前,我和大家繼續(xù)說說流API操作付燥,不知道大家有沒有注意到宣谈,其實我們所有的流API操作都是針對流中的元素進行的,并且都是基于同一流里面的键科,大家有沒有這樣的疑問闻丑,怎么樣把一個流的元素弄到另一個流里面呢漩怎?怎么把流中的一些滿足條件的元素放到一個新流里面呢?通過這一節(jié)嗦嗡,你將會掌握解決剛才問題的本領(lǐng)勋锤。另外再提一點,如果流操作只有中間操作侥祭,沒有終端操作叁执,那么這些中間操作是不會執(zhí)行的,換句話說矮冬,只有終端操作才能觸發(fā)中間操作的運行徒恋。

我們?yōu)槭裁葱枰成洌?/h4>

因為在很多時候,將一個流的元素映射到另一個流對我們是非常有幫助的欢伏。比如有一個包含有名字入挣,手機號碼和錢的數(shù)據(jù)庫構(gòu)成的流,可能你只想要映射錢這個字段到另一個流硝拧,這時候可能之前學到的知識就還不能解決径筏,于是映射就站了出來了。另外障陶,如果你希望對流中的元素應用一些轉(zhuǎn)換滋恬,然后把轉(zhuǎn)換的元素映射到一個新流里面,這時候也可以用映射抱究。

我們先來看看流API庫給我們提供了什么樣的支持

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);//line2
    IntStream mapToInt(ToIntFunction<? super T> mapper);//line3
    LongStream mapToLong(ToLongFunction<? super T> mapper);//line4
    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);//line5
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);//line6
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);//line7
    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);//line8
    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);//line9
}

我和大家分析一個最具有一般性的映射方法map()恢氯,相信大家就能舉一反三了,map()定義如下鼓寺,

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

其中勋拟,R指定新流的元素類型,T指定調(diào)用流的元素類型妈候,mapper是完成映射的Function實例敢靡,被稱為映射函數(shù),映射函數(shù)必須是無狀態(tài)和不干預的(大家對這二個約束條件應該很熟悉了吧)苦银。因為map()方法會返回一個新流啸胧,因此它是一個中間操作。

Functionjava.util.function包中聲明的一個函數(shù)式接口幔虏,聲明如下:

@FunctionalInterface
public interface Function<T, R> {
     R apply(T t);
}

在map()的使有過程中纺念,T是調(diào)用流的元素類型,R是映射的結(jié)果類型想括。其中,apply(T t)中的t是對被映射對象的引用陷谱,被返回映射結(jié)果。下面我們將上一篇中的例子進行變形主胧,用映射來完成他:

假設List里面有三個Integer類型的元素分別為1叭首,2习勤,3。現(xiàn)在的需求是分別讓List里面的每個元素都放大兩倍后焙格,再求積图毕。這個需求的正確答案應該是48;

private static void learnMap() {
     List<Integer> lists = new ArrayList<>();
        lists.add(1);
        lists.add(2);
        lists.add(3);
     //使用并行流來處理
     Integer product = lists.parallelStream().reduce(1, (a, b) -> a *  (b * 2),
                                                        (a, b) -> a * b);
     System.out.println("product:" + product);//48

     //使用映射來處理 
     //Integer productMap = lists.parallelStream().map((a) -> a * 2).reduce(1, (a, b) -> a * b);
     Stream<Integer> productNewMapStream = lists.parallelStream().map((a) -> a * 2);
     Integer productMap = productNewMapStream.reduce(1, (a, b) -> a * b);
     System.out.println("productMap:" + productMap);//48
}

與使用并行流不同,在使用映射處理的時候眷唉,元素擴大2倍發(fā)生時機不一樣了予颤,使用并行流元素擴大是在縮減的過程當中的,而使用映射處理時冬阳,元素擴大是發(fā)生在映射過程中的蛤虐。因此映射過程完程之后,不需要reduce()提供合并器了肝陪。

上面的這個例子還是簡單了一點驳庭,下面再舉一個例子,王者榮耀團隊經(jīng)濟計算:

#玩家使用的英雄以及當前獲得的金幣數(shù)
public class HeroPlayerGold {
    /** 使用的英雄名字 */
    private String hero;
    /** 玩家的ID */
    private String player;
    /** 獲得的金幣數(shù) */
    private int gold;

    public HeroPlayerGold(String hero, String player, int gold) {
        this.hero = hero;
        this.player = player;
        this.gold = gold;
    }
  //省略get/set/toString
}

#玩家獲得的金幣數(shù)
public class Gold {
    /** 獲得的金幣數(shù) */
    private int gold;

    public Gold(int gold) {
        this.gold = gold;
    }
 //省略get/set/toString
}

#測試類
public class Main {
    public static void main(String[] args) {
        learnMap2th();
    }

    private static void learnMap2th() {
        List<HeroPlayerGold> lists = new ArrayList<>();
        lists.add(new HeroPlayerGold("蓋倫", "RNG-Letme", 100));
        lists.add(new HeroPlayerGold("諸葛亮", "RNG-Xiaohu", 300));
        lists.add(new HeroPlayerGold("露娜", "RNG-MLXG", 300));
        lists.add(new HeroPlayerGold("狄仁杰", "RNG-UZI", 500));
        lists.add(new HeroPlayerGold("牛頭", "RNG-Ming", 500));

        //計算團隊經(jīng)濟
        int teamMoney = lists.stream()
                .map(player -> new Gold(player.getGold()))//note1
                .mapToInt(Gold::getGold)
                .reduce(0, (a, b) -> a + b);
        System.out.println("團隊經(jīng)濟:" + teamMoney);//1700


        //計算團隊經(jīng)濟2
        double teamMoney2 = lists.stream()
                .mapToDouble(HeroPlayerGold::getGold)
                .reduce(0, (a, b) -> a + b);
        System.out.println("團隊經(jīng)濟:" + teamMoney2);//1700.0
    }
}

代碼應該不難理解,通過代碼氯窍,大家應該知道我們假設的場景了饲常。我們的RNG去參加王者榮耀比賽了,像這種團隊游戲狼讨,觀眾在經(jīng)濟方面關(guān)注更多的可能是團隊經(jīng)濟贝淤,而不是個人經(jīng)濟。在我們HeroPlayerGold類里面存有明星玩家政供,使用的英雄播聪,和這局比賽某個玩家當前獲得的金幣數(shù),我們另有一個專們管理金幣的Gold類布隔,我們第一種計算團隊經(jīng)濟的方式离陶,使把HeroPlayerGold里面的gold字段轉(zhuǎn)換到Gold里面了 //note1 ,這里產(chǎn)生的新流只包含了原始流中選定的gold字段,因為我們的原始流中包含了hero执泰、player枕磁、gold,三個字段渡蜻,我們只選取了gold字段(因為我們只關(guān)心這個字段)术吝,所以其它的兩個字段被丟棄了。然后從新流取出Gold里面的gold字段并把他轉(zhuǎn)成一個IntStream茸苇,然后我們就要以通過縮減操作完成我們的團隊經(jīng)濟計算了排苍,第一種方式,大家需要好好理解学密,理解了淘衙,我相信你們的項目中,很多很多地方可以用得上了腻暮,再也不需要動不動就查數(shù)據(jù)庫了彤守,怎樣效率高怎樣來毯侦,只是一種建議。第二種只是快速計算團隊經(jīng)濟而已具垫,沒什么值得講的侈离。

接下來講一下他的擴展方向:大家還記得我在第二篇中介紹中間操作概念的時候嗎?中間操作會產(chǎn)生另一個流筝蚕。因此中間操作可以用來創(chuàng)建執(zhí)行一系列動作的管道卦碾。我們可以把多個中間操作放到管道中,所以我們很容易就創(chuàng)建出很強大的組合操作了起宽,發(fā)揮你的想象洲胖,打出你們的組合拳;

我現(xiàn)在舉一個例子:比如現(xiàn)在相統(tǒng)計團隊里面兩個C位的經(jīng)濟占了多少,代碼看起來可能就是這樣了:

 private static void learnMap2th() {
        List<HeroPlayerGold> lists = new ArrayList<>();
        lists.add(new HeroPlayerGold("蓋倫", "RNG-Letme", 100));
        lists.add(new HeroPlayerGold("諸葛亮", "RNG-Xiaohu", 300));
        lists.add(new HeroPlayerGold("露娜", "RNG-MLXG", 300));
        lists.add(new HeroPlayerGold("狄仁杰", "RNG-UZI", 500));
        lists.add(new HeroPlayerGold("牛頭", "RNG-Ming", 500));
        
        //計算兩個C位的經(jīng)濟和
        lists.stream()
                .filter(player-> "RNG-Xiaohu".equals(player.getPlayer()) || "RNG-UZI".equals(player.getPlayer()))
                .map(player->new Gold(player.getGold()))
                .mapToInt(Gold::getGold)
                .reduce((a,b)->a+b)
                .ifPresent(System.out::println);//800
    }

大家有沒有感覺坯沪,這種操作怎么帶有點數(shù)據(jù)庫的風格奥逃场?其實在創(chuàng)建數(shù)據(jù)庫查詢的時候腐晾,這種過濾操作十分常見绘梦,如果你經(jīng)常在你的項目中使用流API,這幾個條件算什么?等你們把流API用熟了之后赴魁,你們完全可以通過這種鏈式操作創(chuàng)建出非常復雜的查詢卸奉,合并和選擇的操作。

通過前面的例子颖御,我們已經(jīng)把map()榄棵,mapToInt()mapToLong()潘拱,mapToDouble都講了疹鳄。那么剩下的就是flatMap()方法了。本來想讓大家自行去理解這個方法的芦岂,因為怕這篇文章寫得太長了瘪弓。但是后面想想,還是我來給大家分析一下吧禽最。

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

通過前面的學習我們知道mapper是一個映射函數(shù)腺怯,它和map()方法也一樣也會返回一個新流,我們把返回的新流稱為映射流川无。我們提供的映射函數(shù)會處理原始流中的每一個元素呛占,而映射流中包含了所有經(jīng)過我們映射函數(shù)處理后產(chǎn)生的新元素。加粗部份需要重點理解懦趋。

我們來看一下源碼對flatMap()的注釋:The flatMap() operation has the effect of applying a one-to-many transformation to the elements of the stream, and then flattening the resulting elements into a new stream.大意就是:flatMap()操作能把原始流中的元素進行一對多的轉(zhuǎn)換晾虑,并且將新生成的元素全都合并到它返回的流里面。根據(jù)我們所學的知識,他的這種一對多的轉(zhuǎn)換功能肯定就是映射函數(shù)提供的帜篇,這一點沒有疑問吧糙捺!然后源碼的注釋上面還提供了一個例子,通過注釋加例子笙隙,我相信大家都能非常清楚地理解flatMap()了继找。

    /* <p>If {@code orders} is a stream of purchase orders, and each purchase
     * order contains a collection of line items, then the following produces a
     * stream containing all the line items in all the orders:
     * <pre>{@code
     *     orders.flatMap(order -> order.getLineItems().stream())...
     * }</pre>
     */

如果orders是一批采購訂單對應的流,并且每一個采購訂單都包含一系列的采購項逃沿,那么orders.flatMap(order -> order.getLineItems().stream())...生成的新流將包含這一批采購訂單中所有采購項婴渡。我們用偽代碼來就更加清晰了 Stream<Orders<OrderItem>> ====>Stream<OrderItem>。大家能理解了嗎凯亮?還沒理解边臼?再來一個例子:

 private static void learnFlatMap() {
        //(廣州  深圳  上海  北京)的全拼的一些組合,下面我們就把每一個城市都劃分一下
        List<String> citys = Arrays.asList("GuangZhou ShangHai", "GuangZhou ShenZhen",
                "ShangHai ShenZhen", "BeiJing ShangHai", "GuangZhou BeiJing", "ShenZhen BeiJing");

        //這里打印的數(shù)組對應的地址
        citys.stream().map(mCitys -> Arrays.stream(mCitys.split(" "))).forEach(System.out::println);//note1

        System.out.println();

        //流里面的元素還是一個數(shù)組
        citys.stream()
                .map(mCities -> Arrays.stream(mCities.split(" ")))//流里面的每個元素還是數(shù)組
                .forEach(cities ->cities.forEach(city-> System.out.print(city+" ")));//note2

        System.out.println();
        System.out.println();

        //直接一個flatMap()就把數(shù)組合并到映射流里面了
        citys.stream().flatMap(mCities->Arrays.stream(mCities.split(" "))).forEach(System.out::println);//note3

        System.out.println();

        //使用distinct()方法去重!
        citys.stream().flatMap(mCities->Arrays.stream(mCities.split(" "))).distinct().forEach(System.out::println);//note4

    }

其中 //note1 處是無法打印元素的假消,使用map()打印元素的方式在 //note2 柠并,原因也在注釋中交待了,但是使用了flatMap()方法后富拗,直接就可以打印了 //note3 臼予,到這里,應該就能理解**如果orders是一批采購訂單對應的流啃沪,并且每一個采購訂單都包含一系列的采購項粘拾,那么orders.flatMap(order -> order.getLineItems().stream())...生成的新流將包含這一批采購訂單中所有采購項。**了吧创千。最后 //note4 是一個去重的方法缰雇,大家運行一遍吧。

小結(jié)一下

通過這一篇文章追驴,相信大家對流API中的映射已經(jīng)不再陌生了械哟,其實最需要注意的一個點是,map()和flatMap()的區(qū)別,我也一步步地帶著大家理解和應用了殿雪。其實在流API這一塊中暇咆,大家單單掌握概念是沒什么用的,一定要去實戰(zhàn)了丙曙,一個項目里面爸业,集合框架這種東西用得還是特別多的,用到集合框架的大部份情況河泳,其實都可以考慮一下用Stream流去操作一下沃呢,不僅增加效率,還可以增加業(yè)務流程的清晰度拆挥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纸兔,更是在濱河造成了極大的恐慌惰瓜,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汉矿,死亡現(xiàn)場離奇詭異崎坊,居然都是意外死亡,警方通過查閱死者的電腦和手機洲拇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門奈揍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赋续,你說我怎么就攤上這事男翰。” “怎么了纽乱?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵蛾绎,是天一觀的道長。 經(jīng)常有香客問我鸦列,道長租冠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任薯嗤,我火速辦了婚禮顽爹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骆姐。我一直安慰自己话原,他們只是感情好,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布诲锹。 她就那樣靜靜地躺著繁仁,像睡著了一般杨赤。 火紅的嫁衣襯著肌膚如雪采够。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天白热,我揣著相機與錄音庸诱,去河邊找鬼捻浦。 笑死,一個胖子當著我的面吹牛桥爽,可吹牛的內(nèi)容都是我干的朱灿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼钠四,長吁一口氣:“原來是場噩夢啊……” “哼盗扒!你這毒婦竟也來了跪楞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤侣灶,失蹤者是張志新(化名)和其女友劉穎甸祭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褥影,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡池户,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凡怎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片校焦。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖统倒,靈堂內(nèi)的尸體忽然破棺而出寨典,到底是詐尸還是另有隱情,我是刑警寧澤檐薯,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布凝赛,位于F島的核電站,受9級特大地震影響坛缕,放射性物質(zhì)發(fā)生泄漏墓猎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一赚楚、第九天 我趴在偏房一處隱蔽的房頂上張望毙沾。 院中可真熱鬧,春花似錦宠页、人聲如沸左胞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烤宙。三九已至,卻和暖如春俭嘁,著一層夾襖步出監(jiān)牢的瞬間躺枕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工供填, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拐云,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓近她,卻偏偏與公主長得像叉瘩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子粘捎,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理薇缅,服務發(fā)現(xiàn)危彩,斷路器,智...
    卡卡羅2017閱讀 134,699評論 18 139
  • 這篇關(guān)于java stream的文章寫的特別好捅暴,轉(zhuǎn)載一下恬砂,以備自己查看咧纠。轉(zhuǎn)載自Java 8 中的 Streams ...
    小白小白啦閱讀 512評論 0 2
  • Jav8中蓬痒,在核心類庫中引入了新的概念,流(Stream)漆羔。流使得程序媛們得以站在更高的抽象層次上對集合進行操作梧奢。...
    仁昌居士閱讀 3,640評論 0 6
  • 本文采用實例驅(qū)動的方式,對JAVA8的stream API進行一個深入的介紹演痒。雖然JAVA8中的stream AP...
    浮梁翁閱讀 25,773評論 3 50
  • 手術(shù)做完了亲轨,我也回來了,坐在這回憶下和你的所有事鸟顺,我應該是7月9號入司的惦蚊,入司呢天沒見到你,只聽師父說咱們小組里還...
    ayan的馬一一閱讀 395評論 0 0