Java函數式編程(七):MapReduce。本站提示廣大學習愛好者:(Java函數式編程(七):MapReduce)文章只能為提供參考,不一定能成為您想要的結果。以下是Java函數式編程(七):MapReduce正文
譯注:map(映照)和reduce(歸約,化簡)是數學上兩個很基本的概念,它們很早就湧現在各類的函數編程說話裡了,直到2003年Google將其發揚光年夜,應用到散布式體系中停止並行盤算後,這個組合的名字才開端在盤算機界年夜放異彩(那些函數式粉能夠其實不這麼以為)。本文我們會看到Java 8在搖身一變支撐函數式編程後,map和reduce組合的初次表態(這裡只是初步引見,後續還會有針對它們的專題)。
對聚集停止歸約
如今為止我們曾經引見了幾個操作聚集的新技能了:查找婚配元素,查找單個元素,聚集轉化。這些操作有一個配合點,它們都是對聚集中的單個元素停止操作。不須要對元素停止比擬,或許對兩個元素停止運算。本節中我們來看一下若何比擬元素,和在遍歷聚集進程中靜態保護一個運算成果。
我們先從簡略的例子開端,然後再按部就班。在第一個例子中,我們先來遍歷一下friends聚集,盤算出一切名字的總字符數。
System.out.println("Total number of characters in all names: " + friends.stream()
.mapToInt(name -> name.length())
.sum());
要算出一切字符的總數我們得曉得每一個名字的長度。經由過程mapToInt()辦法可以輕松的完成這個。當我們曾經把名字轉化成了對應的長度以後,最初只須要把它們加到一塊就好了。我們有一個內置的sum()辦法來完成這個。上面是最初的輸入:
Total number of characters in all names: 26
我們應用了map操作的一個變種,mapToInt()辦法(這類的有mapToInt, mapToDouble等,會對應生成詳細類型的流,好比IntStream,DoubleStream),然後依據前往的長度盤算出總的字符數。
除應用sum辦法,還有許多相似的辦法可使用,好比用max()可以求出最年夜的長度,用min()是最小長度,sorted()對長度停止排序,average()求均勻長度,等等。
上述這個例子還有一個吸惹人的處所就是如今愈來愈風行的MapReduce形式,map()辦法停止映照,而sum()辦法是一個比擬經常使用的reduce操作。現實上,JDK中sum()辦法的完成用的就是reduce()辦法。我們來看下reduce操作更經常使用的一些情勢。
比喻說,我們遍歷一切的名字,然後打印知名字最長的誰人。假如最長的名字有好幾個,我們就打印出最開端找到的誰人。一種辦法是,我們盤算出最年夜的長度,然後選出婚配這個長度的第一個元素。不外如許做須要遍歷兩次列表——效力太低了。這恰是reduce操作上場的時刻了。
我們可以用reduce操作來比擬兩個元素的長度,然後前往最長的誰人,再和剩下的元素做進一步比擬。跟我們之前看到的其余高階函數一樣,reduce()辦法異樣也是遍歷了全部聚集。除此以外,它還記載了lambda表達式前往的盤算成果。有個例子的話可以贊助我們更好的懂得這點,那我們先來看一段代碼吧。
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表達式吸收兩個參數,name1和name2,它會比擬它們的長度,前往最長的誰人。reduce()辦法基本不曉得我們要干甚麼。這個邏輯被剝離到我們傳遞出來的lambda表達式外面了——這是戰略形式的一個輕量級的完成。
這個lambda表達式正好能適配成JDK中一個BinaryOperator的函數式接口的apply辦法。這恰是reduce辦法要接收的參數類型。我們來運轉下這個reduce辦法,看看它可否准確地在兩個最長的名字當選出第一個來。
A longest name: Brian
在reduce()辦法遍歷聚集的進程中,它先對聚集的前兩個元素挪用了lambda表達式,挪用前往的成果持續用於下一次挪用。在第二次挪用中,name1的值被綁定成前次挪用的成果,name2的值則是聚集的第三個元素。殘剩的元素也如許順次挪用下去。最初一次lambda表達式挪用的成果,就是全部reduce()辦法前往的成果。
reduce()辦法前往的是一個Optional值,由於傳遞給它的聚集能夠是空的。那樣的話,也不存在甚麼最長的名字了。假如列表只要一個元素,reduce辦法直接前往誰人元素,不會對lambda表達式停止挪用。
從這個例子中我們可以揣摸出,reduce的成果最多只能夠是聚集中的一個元素。假如我們願望能前往一個默許值或許基本值的話,我們可使用reduce()辦法的一個變種,它可以吸收一個額定的參數。好比,假如最短的名字是Steve,我們可以把它傳給reduce()辦法,像如許:
final String steveOrLonger = friends.stream()
.reduce("Steve", (name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);
假如著名字比它長的,那末這個名字會被選中;不然的話就前往這個基本值Steve。這個版本的reduce()辦法不會前往Optional對象,由於假如聚集是空的,會前往一個默許值;不消斟酌沒有前往值的情形。
在我們停止這章之前 ,我們再來看一下聚集操作外面一個很基本的卻又不是那末輕易的操作:歸並元素。
歸並元素
我們曾經進修了若何停止元素的查找,遍歷,和聚集的轉化。不外還有一個罕見的操作——將聚集元素停止拼接——假如沒有這個新添加的join()函數的話,之前說的簡練和優雅的代碼只能成為泡影了。這個簡略的辦法異常適用以致於它成為JDK裡最經常使用的函數之一。我們來看下若何用它來打印列表中的元素,用逗號停止分隔。
我們照樣用這個friends列表。假如用JDK庫裡的舊辦法的話,想要打印出一切名字並用逗號離隔的話,要做哪些任務?
我們得遍歷列表而且挨個打印元素。Java 5中的for輪回比之前的有所改良,我們就用它吧。
for(String name : friends) {
System.out.print(name + ", ");
}
System.out.println();
代碼很簡略,我們看下它的輸入是甚麼。
Brian, Nate, Neal, Raju, Sara, Scott,
活該,最初多出了一個憎惡的逗號(我們豈非要怪最初的誰人Scott?)。怎樣能讓Java別放一個逗號在這呢?不幸的是,輪回會按步就班的履行,想讓它在最初特別處置一下可不輕易。為懂得決這個成績,我們可以用回本來的那種輪回方法。
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));
我們來看下這個版本的輸入是否是OK。
Brian, Nate, Neal, Raju, Sara, Scott
成果照樣不錯的,不外這個代碼就不敢奉承了。救救我們吧,Java。
我們不消再忍耐這類苦楚了。Java 8裡的StringJoiner類幫我們弄定了這些困難,不止如斯,String類還增長了一個join辦法便利我們可以用一行代碼來替換失落下面那坨器械。
System.out.println(String.join(", ", friends));
快來看下吧,成果跟代碼一樣使人滿足。
Brian, Nate, Neal, Raju, Sara, Scott
成果照樣不錯的,不外這個代碼就不敢奉承了。救救我們吧,Java。
我們不消再忍耐這類苦楚了。Java 8裡的StringJoiner類幫我們弄定了這些困難,不止如斯,String類還增長了一個join辦法便利我們可以用一行代碼來替換失落下面那坨器械。
System.out.println(String.join(", ", friends));
快來看下吧,成果跟代碼一樣使人滿足。
Brian, Nate, Neal, Raju, Sara, Scott
在底層完成中,String.join()辦法挪用了StringJoiner類來將第二個參數傳出去的值(這是個變長參數)拼接成一個長的字符串,用第一個參數作為分隔符。這個辦法固然不止是能拼接逗號這麼簡略了。好比說,我們可以傳入一堆途徑,然後很輕易的拼出一個類途徑(classpath),這可真是多虧了這些新增長的辦法和類。
我們曾經曉得若何去銜接列表元素了,在停止列表銜接前,我們還可以先對元素停止轉化,固然我們也曉得若何應用map辦法來停止列表轉化了。接上去還可以用filter()辦法過濾出我們想要的那些元素。最初一步的銜接列表元素,用逗號照樣甚麼分隔符,不外就是一個簡略的reduce操作罷了了。
我們可以用reduce()辦法將元素拼接成一個字符串,不外這須要我們費點功夫。JDK有一個非常便利的collect()辦法,它也是reduce()的一個變種,我們可以用它來把元素歸並成一個想要的值。
collect()辦法來履行歸約操作,不外它把詳細的操作拜托給一個collector來履行。我們可以把轉化後的元素歸並成一個ArrayList。持續適才誰人例子,我們可以將轉化後的元素,拼接成一個用逗號分隔的字符串。
System.out.println(
friends.stream()
.map(String::toUpperCase)
.collect(joining(", ")));
我們在轉化後的列表上挪用了collect()辦法,給它傳入了一個joining()辦法前往的collector,joining是Collectors對象類裡的一個靜態辦法。collector就像是個吸收器,它吸收collect傳出去的對象,並把它們存儲成你想要的格局:ArrayList, String等。我們會在52頁的collect辦法及Collectors類中進一步摸索這個辦法。
這是輸入的名字,如今它們是年夜寫的,並用逗號離隔。
BRIAN, NATE, NEAL, RAJU, SARA, SCOTT
總結
聚集在編程中非常罕見,有了lambda表達式後,Java的聚集操作變得加倍簡略輕易了。那些拖拉的聚集操作的老代碼都可以換成這類優雅簡練的新方法。外部迭代器使得聚集遍歷,轉化都變得加倍便利,闊別了可變性的懊惱,查找聚集元素也變得異常輕松。應用這些新辦法可以少寫很多代碼。這使得代碼更輕易保護,更聚焦於營業邏輯,編程中的那些根本操作也變得更少了。
下一章中我們會看到lambda表達式若何簡化法式開辟中的另外一個根本操作:字符串操作和對象比擬。