跟我学 Java 8 新特性之 Stream 流(五)映射
轉(zhuǎn)載自? ?跟我學(xué) Java 8 新特性之 Stream 流(五)映射
經(jīng)過(guò)了前面四篇文章的學(xué)習(xí),相信大家對(duì)Stream流已經(jīng)是相當(dāng)?shù)氖煜ち?#xff0c;同時(shí)也掌握了一些高級(jí)功能了,如果你之前有閱讀過(guò)集合框架的基石 Collection?接口,是不是在經(jīng)過(guò)前面的學(xué)習(xí),以前看不懂的東西,突然之間就恍然大悟了呢?
今天我們的主角是Stream流里面的映射。由于之前,映射并沒(méi)有再我們的Demo,例子中出現(xiàn)過(guò),所以對(duì)大家來(lái)說(shuō)可能會(huì)稍微有一點(diǎn)點(diǎn)陌生的,但通過(guò)這一篇文章,我相信能解決你的疑問(wèn)。
在正式開(kāi)始之前,我和大家繼續(xù)說(shuō)說(shuō)流API操作,不知道大家有沒(méi)有注意到,其實(shí)我們所有的流API操作都是針對(duì)流中的元素進(jìn)行的,并且都是基于同一流里面的,大家有沒(méi)有這樣的疑問(wèn),怎么樣把一個(gè)流的元素弄到另一個(gè)流里面呢?怎么把流中的一些滿足條件的元素放到一個(gè)新流里面呢?
通過(guò)這一節(jié),你將會(huì)掌握解決剛才問(wèn)題的本領(lǐng)。另外再提一點(diǎn),如果流操作只有中間操作,沒(méi)有終端操作,那么這些中間操作是不會(huì)執(zhí)行的,換句話說(shuō),只有終端操作才能觸發(fā)中間操作的運(yùn)行。
我們?yōu)槭裁葱枰成?#xff1f;
因?yàn)樵诤芏鄷r(shí)候,將一個(gè)流的元素映射到另一個(gè)流對(duì)我們是非常有幫助的。比如有一個(gè)包含有名字,手機(jī)號(hào)碼和錢的數(shù)據(jù)庫(kù)構(gòu)成的流,可能你只想要映射錢這個(gè)字段到另一個(gè)流,這時(shí)候可能之前學(xué)到的知識(shí)就還不能解決,于是映射就站了出來(lái)了。
另外,如果你希望對(duì)流中的元素應(yīng)用一些轉(zhuǎn)換,然后把轉(zhuǎn)換的元素映射到一個(gè)新流里面,這時(shí)候也可以用映射。
我們先來(lái)看看流API庫(kù)給我們提供了什么樣的支持
public interface Stream<T> extends BaseStream<T, Stream<T>> {<R> Stream<R> map(Function<? super T, ? extends R> mapper);//line2IntStream mapToInt(ToIntFunction<? super T> mapper);//line3LongStream mapToLong(ToLongFunction<? super T> mapper);//line4DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);//line5<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);//line6IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);//line7LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);//line8DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);//line9}我和大家分析一個(gè)最具有一般性的映射方法map(),相信大家就能舉一反三了,map()定義如下,
Streammap(Function mapper);
其中,R指定新流的元素類型,T指定調(diào)用流的元素類型,mapper是完成映射的Function實(shí)例,被稱為映射函數(shù),映射函數(shù)必須是無(wú)狀態(tài)和不干預(yù)的(大家對(duì)這二個(gè)約束條件應(yīng)該很熟悉了吧)。因?yàn)閙ap()方法會(huì)返回一個(gè)新流,因此它是一個(gè)中間操作。
Function是 java.util.function包中聲明的一個(gè)函數(shù)式接口,聲明如下:
@FunctionalInterfacepublic interface Function<T, R> {R apply(T t);}在map()的使有過(guò)程中,T是調(diào)用流的元素類型,R是映射的結(jié)果類型。其中,apply(T t)中的t是對(duì)被映射對(duì)象的引用,被返回映射結(jié)果。下面我們將上一篇中的例子進(jìn)行變形,用映射來(lái)完成他:
假設(shè)List里面有三個(gè)Integer類型的元素分別為1,2,3?,F(xiàn)在的需求是分別讓List里面的每個(gè)元素都放大兩倍后,再求積。這個(gè)需求的正確答案應(yīng)該是48;
private static void learnMap() {List<Integer> lists = new ArrayList<>();lists.add(1);lists.add(2);lists.add(3);//使用并行流來(lái)處理Integer product = lists.parallelStream().reduce(1, (a, b) -> a * ?(b * 2),(a, b) -> a * b);System.out.println("product:" + product);//48//使用映射來(lái)處理//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}與使用并行流不同,在使用映射處理的時(shí)候,元素?cái)U(kuò)大2倍發(fā)生時(shí)機(jī)不一樣了,使用并行流元素?cái)U(kuò)大是在縮減的過(guò)程當(dāng)中的,而使用映射處理時(shí),元素?cái)U(kuò)大是發(fā)生在映射過(guò)程中的。因此映射過(guò)程完程之后,不需要reduce()提供合并器了。
上面的這個(gè)例子還是簡(jiǎn)單了一點(diǎn),下面再舉一個(gè)例子,王者榮耀團(tuán)隊(duì)經(jīng)濟(jì)計(jì)算:
#玩家使用的英雄以及當(dā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}#測(cè)試類 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ì)算團(tuán)隊(duì)經(jīng)濟(jì)int teamMoney = lists.stream().map(player -> new Gold(player.getGold()))//note1.mapToInt(Gold::getGold).reduce(0, (a, b) -> a + b);System.out.println("團(tuán)隊(duì)經(jīng)濟(jì):" + teamMoney);//1700//計(jì)算團(tuán)隊(duì)經(jīng)濟(jì)2double teamMoney2 = lists.stream().mapToDouble(HeroPlayerGold::getGold).reduce(0, (a, b) -> a + b);System.out.println("團(tuán)隊(duì)經(jīng)濟(jì):" + teamMoney2);//1700.0} }代碼應(yīng)該不難理解,通過(guò)代碼,大家應(yīng)該知道我們假設(shè)的場(chǎng)景了。我們的RNG去參加王者榮耀比賽了,像這種團(tuán)隊(duì)游戲,觀眾在經(jīng)濟(jì)方面關(guān)注更多的可能是團(tuán)隊(duì)經(jīng)濟(jì),而不是個(gè)人經(jīng)濟(jì)。
在我們 HeroPlayerGold類里面存有明星玩家,使用的英雄,和這局比賽某個(gè)玩家當(dāng)前獲得的金幣數(shù),我們另有一個(gè)專們管理金幣的 Gold類,我們第一種計(jì)算團(tuán)隊(duì)經(jīng)濟(jì)的方式,使把 HeroPlayerGold里面的 gold字段轉(zhuǎn)換到 Gold里面了 //note1 ,這里產(chǎn)生的新流只包含了原始流中選定的 gold字段,因?yàn)槲覀兊脑剂髦邪?hero、 player、 gold,三個(gè)字段,我們只選取了 gold字段(因?yàn)槲覀冎魂P(guān)心這個(gè)字段),所以其它的兩個(gè)字段被丟棄了。然后從新流取出 Gold里面的 gold字段并把他轉(zhuǎn)成一個(gè) IntStream,然后我們就要以通過(guò)縮減操作完成我們的團(tuán)隊(duì)經(jīng)濟(jì)計(jì)算了。
第一種方式,大家需要好好理解,理解了,我相信你們的項(xiàng)目中,很多很多地方可以用得上了,再也不需要?jiǎng)硬粍?dòng)就查數(shù)據(jù)庫(kù)了,怎樣效率高怎樣來(lái),只是一種建議。第二種只是快速計(jì)算團(tuán)隊(duì)經(jīng)濟(jì)而已,沒(méi)什么值得講的。
接下來(lái)講一下他的擴(kuò)展方向:大家還記得我在第二篇中介紹中間操作概念的時(shí)候嗎? 中間操作會(huì)產(chǎn)生另一個(gè)流。因此中間操作可以用來(lái)創(chuàng)建執(zhí)行一系列動(dòng)作的管道。我們可以把多個(gè)中間操作放到管道中,所以我們很容易就創(chuàng)建出很強(qiáng)大的組合操作了,發(fā)揮你的想象,打出你們的組合拳;
我現(xiàn)在舉一個(gè)例子:比如現(xiàn)在相統(tǒng)計(jì)團(tuán)隊(duì)里面兩個(gè)C位的經(jīng)濟(jì)占了多少,代碼看起來(lái)可能就是這樣了:
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ì)算兩個(gè)C位的經(jīng)濟(jì)和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}大家有沒(méi)有感覺(jué),這種操作怎么帶有點(diǎn)數(shù)據(jù)庫(kù)的風(fēng)格啊?其實(shí)在創(chuàng)建數(shù)據(jù)庫(kù)查詢的時(shí)候,這種過(guò)濾操作十分常見(jiàn),如果你經(jīng)常在你的項(xiàng)目中使用流API,這幾個(gè)條件算什么?等你們把流API用熟了之后,你們完全可以通過(guò)這種鏈?zhǔn)讲僮鲃?chuàng)建出非常復(fù)雜的查詢,合并和選擇的操作。
通過(guò)前面的例子,我們已經(jīng)把 map(), mapToInt(), mapToLong(), mapToDouble都講了。那么剩下的就是flatMap()方法了。本來(lái)想讓大家自行去理解這個(gè)方法的,因?yàn)榕逻@篇文章寫(xiě)得太長(zhǎng)了。但是后面想想,還是我來(lái)給大家分析一下吧。
StreamflatMap(Function> mapper);
通過(guò)前面的學(xué)習(xí)我們知道 mapper是一個(gè)映射函數(shù),它和map()方法也一樣也會(huì)返回一個(gè)新流,我們把返回的新流稱為映射流。我們提供的映射函數(shù)會(huì)處理原始流中的每一個(gè)元素,而映射流中包含了所有經(jīng)過(guò)我們映射函數(shù)處理后產(chǎn)生的新元素。 加粗部份需要重點(diǎn)理解。
我們來(lái)看一下源碼對(duì)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()操作能把原始流中的元素進(jìn)行一對(duì)多的轉(zhuǎn)換,并且將新生成的元素全都合并到它返回的流里面。根據(jù)我們所學(xué)的知識(shí),他的這種一對(duì)多的轉(zhuǎn)換功能肯定就是映射函數(shù)提供的,這一點(diǎn)沒(méi)有疑問(wèn)吧!然后源碼的注釋上面還提供了一個(gè)例子,通過(guò)注釋加例子,我相信大家都能非常清楚地理解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是一批采購(gòu)訂單對(duì)應(yīng)的流,并且每一個(gè)采購(gòu)訂單都包含一系列的采購(gòu)項(xiàng),那么 orders.flatMap(order->order.getLineItems().stream())...生成的新流將包含這一批采購(gòu)訂單中所有采購(gòu)項(xiàng)。
我們用偽代碼來(lái)就更加清晰了 Stream<Orders<OrderItem>>====>Stream。大家能理解了嗎?還沒(méi)理解?再來(lái)一個(gè)例子:
private static void learnFlatMap() {//(廣州 ?深圳 ?上海 ?北京)的全拼的一些組合,下面我們就把每一個(gè)城市都劃分一下List<String> citys = Arrays.asList("GuangZhou ShangHai", "GuangZhou ShenZhen","ShangHai ShenZhen", "BeiJing ShangHai", "GuangZhou BeiJing", "ShenZhen BeiJing");//這里打印的數(shù)組對(duì)應(yīng)的地址citys.stream().map(mCitys -> Arrays.stream(mCitys.split(" "))).forEach(System.out::println);//note1System.out.println();//流里面的元素還是一個(gè)數(shù)組citys.stream().map(mCities -> Arrays.stream(mCities.split(" ")))//流里面的每個(gè)元素還是數(shù)組.forEach(cities ->cities.forEach(city-> System.out.print(city+" ")));//note2System.out.println();System.out.println();//直接一個(gè)flatMap()就把數(shù)組合并到映射流里面了citys.stream().flatMap(mCities->Arrays.stream(mCities.split(" "))).forEach(System.out::println);//note3System.out.println();//使用distinct()方法去重!citys.stream().flatMap(mCities->Arrays.stream(mCities.split(" "))).distinct().forEach(System.out::println);//note4}其中 //note1 處是無(wú)法打印元素的,使用map()打印元素的方式在 //note2 ,原因也在注釋中交待了,但是使用了flatMap()方法后,直接就可以打印了 //note3 ,到這里,應(yīng)該就能理解如果orders是一批采購(gòu)訂單對(duì)應(yīng)的流,并且每一個(gè)采購(gòu)訂單都包含一系列的采購(gòu)項(xiàng),那么 orders.flatMap(order->order.getLineItems().stream())...生成的新流將包含這一批采購(gòu)訂單中所有采購(gòu)項(xiàng)。 了吧。最后 //note4 是一個(gè)去重的方法,大家運(yùn)行一遍吧。
小結(jié)一下
通過(guò)這一篇文章,相信大家對(duì)流API中的映射已經(jīng)不再陌生了,其實(shí)最需要注意的一個(gè)點(diǎn)是,map()和flatMap()的區(qū)別,我也一步步地帶著大家理解和應(yīng)用了。其實(shí)在流API這一塊中,大家單單掌握概念是沒(méi)什么用的,一定要去實(shí)戰(zhàn)了,一個(gè)項(xiàng)目里面,集合框架這種東西用得還是特別多的,用到集合框架的大部份情況,其實(shí)都可以考慮一下用Stream流去操作一下,不僅增加效率,還可以增加業(yè)務(wù)流程的清晰度。
總結(jié)
以上是生活随笔為你收集整理的跟我学 Java 8 新特性之 Stream 流(五)映射的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 两步验证杀手锏:Java 接入 Goog
- 下一篇: java美元兑换,(Java实现) 美元