欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java函數(shù)式編程(七):MapReduce

 更新時(shí)間:2014年09月26日 09:10:23   作者:有孚  
這篇文章主要介紹了Java函數(shù)式編程(七):MapReduce,本文是系列文章的第7篇,其它文章請(qǐng)參閱本文底部的相關(guān)文章,需要的朋友可以參考下

譯注:map(映射)和reduce(歸約,化簡(jiǎn))是數(shù)學(xué)上兩個(gè)很基礎(chǔ)的概念,它們很早就出現(xiàn)在各類的函數(shù)編程語(yǔ)言里了,直到2003年Google將其發(fā)揚(yáng)光大,運(yùn)用到分布式系統(tǒng)中進(jìn)行并行計(jì)算后,這個(gè)組合的名字才開始在計(jì)算機(jī)界大放異彩(那些函數(shù)式粉可能并不這么認(rèn)為)。本文我們會(huì)看到Java 8在搖身一變支持函數(shù)式編程后,map和reduce組合的首次亮相(這里只是初步介紹,后續(xù)還會(huì)有針對(duì)它們的專題)。

對(duì)集合進(jìn)行歸約

現(xiàn)在為止我們已經(jīng)介紹了幾個(gè)操作集合的新技巧了:查找匹配元素,查找單個(gè)元素,集合轉(zhuǎn)化。這些操作有一個(gè)共同點(diǎn),它們都是對(duì)集合中的單個(gè)元素進(jìn)行操作。不需要對(duì)元素進(jìn)行比較,或者對(duì)兩個(gè)元素進(jìn)行運(yùn)算。本節(jié)中我們來(lái)看一下如何比較元素,以及在遍歷集合過程中動(dòng)態(tài)維護(hù)一個(gè)運(yùn)算結(jié)果。

我們先從簡(jiǎn)單的例子開始,然后再循序漸進(jìn)。在第一個(gè)例子中,我們先來(lái)遍歷一下friends集合,計(jì)算出所有名字的總字符數(shù)。

復(fù)制代碼 代碼如下:

System.out.println("Total number of characters in all names: " + friends.stream()
         .mapToInt(name -> name.length())
         .sum());

要算出所有字符的總數(shù)我們得知道每個(gè)名字的長(zhǎng)度。通過mapToInt()方法可以輕松的完成這個(gè)。當(dāng)我們已經(jīng)把名字轉(zhuǎn)化成了對(duì)應(yīng)的長(zhǎng)度之后,最后只需要把它們加到一塊就行了。我們有一個(gè)內(nèi)置的sum()方法來(lái)完成這個(gè)。下面是最后的輸出:

復(fù)制代碼 代碼如下:

Total number of characters in all names: 26

我們使用了map操作的一個(gè)變種,mapToInt()方法(這種的有mapToInt, mapToDouble等,會(huì)對(duì)應(yīng)生成具體類型的流,比如IntStream,DoubleStream),然后根據(jù)返回的長(zhǎng)度計(jì)算出總的字符數(shù)。

除了使用sum方法,還有很多類似的方法可以使用,比如用max()可以求出最大的長(zhǎng)度,用min()是最小長(zhǎng)度,sorted()對(duì)長(zhǎng)度進(jìn)行排序,average()求平均長(zhǎng)度,等等。

上述這個(gè)例子還有一個(gè)吸引人的地方就是現(xiàn)在越來(lái)越流行的MapReduce模式,map()方法進(jìn)行映射,而sum()方法是一個(gè)比較常用的reduce操作。事實(shí)上,JDK中sum()方法的實(shí)現(xiàn)用的就是reduce()方法。我們來(lái)看下reduce操作更常用的一些形式。

比方說(shuō),我們遍歷所有的名字,然后打印出名字最長(zhǎng)的那個(gè)。如果最長(zhǎng)的名字有好幾個(gè),我們就打印出最開始找到的那個(gè)。一種方法是,我們計(jì)算出最大的長(zhǎng)度,然后選出匹配這個(gè)長(zhǎng)度的第一個(gè)元素。不過這樣做需要遍歷兩次列表——效率太低了。這正是reduce操作上場(chǎng)的時(shí)候了。

我們可以用reduce操作來(lái)比較兩個(gè)元素的長(zhǎng)度,然后返回最長(zhǎng)的那個(gè),再和剩下的元素做進(jìn)一步比較。跟我們之前看到的別的高階函數(shù)一樣,reduce()方法同樣也是遍歷了整個(gè)集合。除此之外,它還記錄了lambda表達(dá)式返回的計(jì)算結(jié)果。有個(gè)例子的話可以幫助我們更好的理解這點(diǎn),那我們先來(lái)看一段代碼吧。

復(fù)制代碼 代碼如下:

final Optional<String> aLongName = friends.stream()
         .reduce((name1, name2) ->
            name1.length() >= name2.length() ? name1 : name2);
aLongName.ifPresent(name ->
System.out.println(String.format("A longest name: %s", name)));

傳給reduce()方法的lambda表達(dá)式接收兩個(gè)參數(shù),name1和name2,它會(huì)比較它們的長(zhǎng)度,返回最長(zhǎng)的那個(gè)。reduce()方法根本不知道我們要干什么。這個(gè)邏輯被剝離到我們傳遞進(jìn)去的lambda表達(dá)式里面了——這是策略模式的一個(gè)輕量級(jí)的實(shí)現(xiàn)。

這個(gè)lambda表達(dá)式正好能適配成JDK中一個(gè)BinaryOperator的函數(shù)式接口的apply方法。這正是reduce方法要接受的參數(shù)類型。我們來(lái)運(yùn)行下這個(gè)reduce方法,看看它能否正確地在兩個(gè)最長(zhǎng)的名字中選出第一個(gè)來(lái)。

復(fù)制代碼 代碼如下:

A longest name: Brian

在reduce()方法遍歷集合的過程中,它先對(duì)集合的前兩個(gè)元素調(diào)用了lambda表達(dá)式,調(diào)用返回的結(jié)果繼續(xù)用于下一次調(diào)用。在第二次調(diào)用中,name1的值被綁定成上次調(diào)用的結(jié)果,name2的值則是集合的第三個(gè)元素。剩余的元素也這樣依次調(diào)用下去。最后一次lambda表達(dá)式調(diào)用的結(jié)果,就是整個(gè)reduce()方法返回的結(jié)果。

reduce()方法返回的是一個(gè)Optional值,因?yàn)閭鬟f給它的集合可能是空的。那樣的話,也不存在什么最長(zhǎng)的名字了。如果列表只有一個(gè)元素,reduce方法直接返回那個(gè)元素,不會(huì)對(duì)lambda表達(dá)式進(jìn)行調(diào)用。

從這個(gè)例子中我們可以推斷出,reduce的結(jié)果最多只可能是集合中的一個(gè)元素。如果我們希望能返回一個(gè)默認(rèn)值或者基礎(chǔ)值的話,我們可以使用reduce()方法的一個(gè)變種,它可以接收一個(gè)額外的參數(shù)。比如,如果最短的名字是Steve,我們可以把它傳給reduce()方法,像這樣:

復(fù)制代碼 代碼如下:

final String steveOrLonger = friends.stream()
     .reduce("Steve", (name1, name2) ->
            name1.length() >= name2.length() ? name1 : name2);

如果有名字比它長(zhǎng)的,那么這個(gè)名字會(huì)被選中;否則的話就返回這個(gè)基礎(chǔ)值Steve。這個(gè)版本的reduce()方法不會(huì)返回Optional對(duì)象,因?yàn)槿绻鲜强盏模瑫?huì)返回一個(gè)默認(rèn)值;不用考慮沒有返回值的情況。

在我們結(jié)束這章之前 ,我們?cè)賮?lái)看一下集合操作里面一個(gè)很基礎(chǔ)的卻又不是那么容易的操作:合并元素。

合并元素

我們已經(jīng)學(xué)習(xí)了如何進(jìn)行元素的查找,遍歷,以及集合的轉(zhuǎn)化。不過還有一個(gè)常見的操作——將集合元素進(jìn)行拼接——如果沒有這個(gè)新添加的join()函數(shù)的話,之前說(shuō)的簡(jiǎn)潔和優(yōu)雅的代碼只能成為泡影了。這個(gè)簡(jiǎn)單的方法非常實(shí)用以至于它成為JDK里最常用的函數(shù)之一。我們來(lái)看下如何用它來(lái)打印列表中的元素,用逗號(hào)進(jìn)行分隔。

我們還是用這個(gè)friends列表。如果用JDK庫(kù)里的舊方法的話,想要打印出所有名字并用逗號(hào)隔開的話,要做哪些工作?

我們得遍歷列表并且挨個(gè)打印元素。Java 5中的for循環(huán)比之前的有所改進(jìn),我們就用它吧。

復(fù)制代碼 代碼如下:

for(String name : friends) {
      System.out.print(name + ", ");
}
System.out.println();

代碼很簡(jiǎn)單,我們看下它的輸出是什么。
復(fù)制代碼 代碼如下:

Brian, Nate, Neal, Raju, Sara, Scott,

該死,最后多出了一個(gè)討厭的逗號(hào)(我們難道要怪最后的那個(gè)Scott?)。怎么能讓Java別放一個(gè)逗號(hào)在這呢?不幸的是,循環(huán)會(huì)按步就班的執(zhí)行,想讓它在最后特殊處理一下可不容易。為了解決這個(gè)問題,我們可以用回原來(lái)的那種循環(huán)方式。

復(fù)制代碼 代碼如下:

for(int i = 0; i < friends.size() - 1; i++) {
      System.out.print(friends.get(i) + ", ");
}
if(friends.size() > 0)
      System.out.println(friends.get(friends.size() - 1));

我們來(lái)看下這個(gè)版本的輸出是不是OK。
復(fù)制代碼 代碼如下:

Brian, Nate, Neal, Raju, Sara, Scott

結(jié)果還是不錯(cuò)的,不過這個(gè)代碼就不敢恭維了。救救我們吧,Java。

我們不用再忍受這種痛苦了。Java 8里的StringJoiner類幫我們搞定了這些難題,不止如此,String類還增加了一個(gè)join方法方便我們可以用一行代碼來(lái)替代掉上面那坨東西。

復(fù)制代碼 代碼如下:

System.out.println(String.join(", ", friends));

快來(lái)看下吧,結(jié)果跟代碼一樣令人滿意。
復(fù)制代碼 代碼如下:

Brian, Nate, Neal, Raju, Sara, Scott

結(jié)果還是不錯(cuò)的,不過這個(gè)代碼就不敢恭維了。救救我們吧,Java。

我們不用再忍受這種痛苦了。Java 8里的StringJoiner類幫我們搞定了這些難題,不止如此,String類還增加了一個(gè)join方法方便我們可以用一行代碼來(lái)替代掉上面那坨東西。

復(fù)制代碼 代碼如下:

System.out.println(String.join(", ", friends));

快來(lái)看下吧,結(jié)果跟代碼一樣令人滿意。
復(fù)制代碼 代碼如下:

Brian, Nate, Neal, Raju, Sara, Scott

在底層實(shí)現(xiàn)中,String.join()方法調(diào)用了StringJoiner類來(lái)將第二個(gè)參數(shù)傳進(jìn)來(lái)的值(這是個(gè)變長(zhǎng)參數(shù))拼接成一個(gè)長(zhǎng)的字符串,用第一個(gè)參數(shù)作為分隔符。這個(gè)方法當(dāng)然不止是能拼接逗號(hào)這么簡(jiǎn)單了。比如說(shuō),我們可以傳入一堆路徑,然后很容易的拼出一個(gè)類路徑(classpath),這可真是多虧了這些新增加的方法和類。

我們已經(jīng)知道如何去連接列表元素了,在進(jìn)行列表連接前,我們還可以先對(duì)元素進(jìn)行轉(zhuǎn)化,當(dāng)然我們也知道如何使用map方法來(lái)進(jìn)行列表轉(zhuǎn)化了。接下來(lái)還可以用filter()方法過濾出我們想要的那些元素。最后一步的連接列表元素,用逗號(hào)還是什么分隔符,不過就是一個(gè)簡(jiǎn)單的reduce操作而已了。

我們可以用reduce()方法將元素拼接成一個(gè)字符串,不過這需要我們費(fèi)點(diǎn)工夫。JDK有一個(gè)十分方便的collect()方法,它也是reduce()的一個(gè)變種,我們可以用它來(lái)把元素合并成一個(gè)想要的值。

collect()方法來(lái)執(zhí)行歸約操作,不過它把具體的操作委托給一個(gè)collector來(lái)執(zhí)行。我們可以把轉(zhuǎn)化后的元素合并成一個(gè)ArrayList。繼續(xù)剛才那個(gè)例子,我們可以將轉(zhuǎn)化后的元素,拼接成一個(gè)用逗號(hào)分隔的字符串。

復(fù)制代碼 代碼如下:

System.out.println(
      friends.stream()
          .map(String::toUpperCase)
          .collect(joining(", ")));

我們?cè)谵D(zhuǎn)化后的列表上調(diào)用了collect()方法,給它傳入了一個(gè)joining()方法返回的collector,joining是Collectors工具類里的一個(gè)靜態(tài)方法。collector就像是個(gè)接收器,它接收collect傳進(jìn)來(lái)的對(duì)象,并把它們存儲(chǔ)成你想要的格式:ArrayList, String等。我們會(huì)在52頁(yè)的collect方法及Collectors類中進(jìn)一步探索這個(gè)方法。

這是輸出的名字,現(xiàn)在它們是大寫的,并用逗號(hào)隔開。

復(fù)制代碼 代碼如下:

BRIAN, NATE, NEAL, RAJU, SARA, SCOTT

總結(jié)

集合在編程中十分常見,有了lambda表達(dá)式后,Java的集合操作變得更加簡(jiǎn)單容易了。那些拖沓的集合操作的老代碼都可以換成這種優(yōu)雅簡(jiǎn)潔的新方式。內(nèi)部迭代器使得集合遍歷,轉(zhuǎn)化都變得更加方便,遠(yuǎn)離了可變性的煩惱,查找集合元素也變得異常輕松。使用這些新方法可以少寫不少代碼。這使得代碼更容易維護(hù),更聚焦于業(yè)務(wù)邏輯,編程中的那些基本操作也變得更少了。

下一章中我們會(huì)看到lambda表達(dá)式如何簡(jiǎn)化程序開發(fā)中的另一個(gè)基本操作:字符串操作以及對(duì)象比較。

相關(guān)文章

  • ASM源碼學(xué)習(xí)之ClassReader、ClassVisitor與ClassWriter詳解

    ASM源碼學(xué)習(xí)之ClassReader、ClassVisitor與ClassWriter詳解

    這篇文章主要給大家介紹了ASM源碼之ClassReader、ClassVisitor與ClassWriter的相關(guān)資料,文中介紹的非常相信,相信對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,有需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-01-01
  • JAVA TIMER簡(jiǎn)單用法學(xué)習(xí)

    JAVA TIMER簡(jiǎn)單用法學(xué)習(xí)

    Timer類是用來(lái)執(zhí)行任務(wù)的類,它接受一個(gè)TimerTask做參數(shù)
    2013-07-07
  • 一文搞懂Spring中的Bean作用域

    一文搞懂Spring中的Bean作用域

    scope用來(lái)聲明容器中的對(duì)象所應(yīng)該處的限定場(chǎng)景或者說(shuō)該對(duì)象的存活時(shí)間,即容器在對(duì)象進(jìn)入其 相應(yīng)的scope之前,生成并裝配這些對(duì)象,在該對(duì)象不再處于這些scope的限定之后,容器通常會(huì)銷毀這些對(duì)象,這篇文章主要介紹了Spring中的Bean作用域,需要的朋友可以參考下
    2022-06-06
  • 如何解決IDEA中JSP頁(yè)面部分出現(xiàn)綠色背景色問題

    如何解決IDEA中JSP頁(yè)面部分出現(xiàn)綠色背景色問題

    這篇文章主要介紹了如何解決IDEA中JSP頁(yè)面部分出現(xiàn)綠色背景色問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • SpringBoot 如何優(yōu)雅的實(shí)現(xiàn)跨服務(wù)器上傳文件的示例

    SpringBoot 如何優(yōu)雅的實(shí)現(xiàn)跨服務(wù)器上傳文件的示例

    這篇文章主要介紹了SpringBoot 如何優(yōu)雅的實(shí)現(xiàn)跨服務(wù)器上傳文件的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2006-11-11
  • Java中的Unsafe在安全領(lǐng)域的使用總結(jié)和復(fù)現(xiàn)(實(shí)例詳解)

    Java中的Unsafe在安全領(lǐng)域的使用總結(jié)和復(fù)現(xiàn)(實(shí)例詳解)

    unsafe里面有很多好用的方法,比如allocateInstance可以直接創(chuàng)建實(shí)例對(duì)象,defineAnonymousClass可以創(chuàng)建一個(gè)VM匿名類(VM?Anonymous?Class),以及直接從內(nèi)存級(jí)別修改對(duì)象的值。這篇文章主要介紹了Java中的Unsafe在安全領(lǐng)域的一些應(yīng)用總結(jié)和復(fù)現(xiàn),需要的朋友可以參考下
    2022-03-03
  • 一文帶你入門JDK8新特性——Lambda表達(dá)式

    一文帶你入門JDK8新特性——Lambda表達(dá)式

    這篇文章主要介紹了JDK8新特性——Lambda表達(dá)式的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)JAVA開發(fā),感興趣的朋友可以了解下
    2020-08-08
  • Java中的ScheduledThreadPoolExecutor定時(shí)任務(wù)詳解

    Java中的ScheduledThreadPoolExecutor定時(shí)任務(wù)詳解

    這篇文章主要介紹了Java中的ScheduledThreadPoolExecutor詳解,??ScheduledThreadPoolExecutor?繼承自?ThreadPoolExecutor,它主要用來(lái)在給定的延遲之后運(yùn)行任務(wù),或者定期執(zhí)行任務(wù),ScheduledThreadPoolExecutor?的功能與?Timer?類似<BR>,需要的朋友可以參考下
    2023-12-12
  • Java平閏年判斷的方法總結(jié)

    Java平閏年判斷的方法總結(jié)

    本篇文章給大家整理了Java平閏年判斷的兩種方法,大家在寫程序的時(shí)候如果用的到參考下吧。
    2018-02-02
  • Java超詳細(xì)整理講解各種排序

    Java超詳細(xì)整理講解各種排序

    這篇文章主要介紹了Java常用的排序算法及代碼實(shí)現(xiàn),在Java開發(fā)中,對(duì)排序的應(yīng)用需要熟練的掌握,這樣才能夠確保Java學(xué)習(xí)時(shí)候能夠有扎實(shí)的基礎(chǔ)能力。那Java有哪些排序算法呢?本文小編就來(lái)詳細(xì)說(shuō)說(shuō)Java常見的排序算法,需要的朋友可以參考一下
    2022-07-07

最新評(píng)論