就在今年 Java 25周歲了,可能比在座的各位中的一些少年年齡還大黄刚,但令人遺憾的是车海,竟然沒(méi)有我大,不禁感嘆隘击,Java 還是太小了侍芝。(難道我會(huì)說(shuō)是因?yàn)槲依狭耍浚?/p>
而就在上個(gè)月埋同,Java 15 的試驗(yàn)版悄悄發(fā)布了州叠,但是在 Java 界一直有個(gè)神秘現(xiàn)象,那就是「你發(fā)你發(fā)任你發(fā)凶赁,我的最愛(ài) Java 8」.
據(jù) Snyk 和 The Java Magazine 聯(lián)合推出發(fā)布的 2020 JVM 生態(tài)調(diào)查報(bào)告顯示咧栗,在所有的 Java 版本中,仍然有 64% 的開(kāi)發(fā)者使用 Java 8虱肄。另外一些開(kāi)發(fā)者可能已經(jīng)開(kāi)始用 Java 9致板、Java 11、Java 13 了咏窿,當(dāng)然還有一些神仙開(kāi)發(fā)者還在堅(jiān)持使用 JDK 1.6 和 1.7斟或。
盡管 Java 8 發(fā)布多年,使用者眾多集嵌,可神奇的是竟然有很多同學(xué)沒(méi)有用過(guò) Java 8 的新特性萝挤,比如?Lambda表達(dá)式、比如方法引用根欧,再比如今天要說(shuō)的?Stream怜珍。其實(shí) Stream 就是以 Lambda 和方法引用為基礎(chǔ),封裝的簡(jiǎn)單易用凤粗、函數(shù)式風(fēng)格的 API酥泛。
Java 8 是在 2014 年發(fā)布的,實(shí)話說(shuō)嫌拣,風(fēng)箏我也是在 Java 8 發(fā)布后很長(zhǎng)一段時(shí)間才用的 Stream柔袁,因?yàn)?Java 8 發(fā)布的時(shí)候我還在 C# 的世界中掙扎,而使用 Lambda 表達(dá)式卻很早了亭罪,因?yàn)?Python 中用 Lambda 很方便瘦馍,沒(méi)錯(cuò),我寫 Python 的時(shí)間要比 Java 的時(shí)間還長(zhǎng)应役。
要講 Stream 情组,那就不得不先說(shuō)一下它的左膀右臂 Lambda 和方法引用燥筷,你用的 Stream API 其實(shí)就是函數(shù)式的編程風(fēng)格,其中的「函數(shù)」就是方法引用院崇,「式」就是 Lambda 表達(dá)式肆氓。
Lambda 表達(dá)式
Lambda 表達(dá)式是一個(gè)匿名函數(shù),Lambda表達(dá)式基于數(shù)學(xué)中的λ演算得名底瓣,直接對(duì)應(yīng)于其中的lambda抽象谢揪,是一個(gè)匿名函數(shù),即沒(méi)有函數(shù)名的函數(shù)捐凭。Lambda表達(dá)式可以表示閉包拨扶。
在 Java 中,Lambda 表達(dá)式的格式是像下面這樣
// 無(wú)參數(shù)茁肠,無(wú)返回值() -> log.info("Lambda") // 有參數(shù)患民,有返回值(int a, int b) -> { a+b }
其等價(jià)于
log.info("Lambda");privateintplus(inta,intb){returna+b;}
最常見(jiàn)的一個(gè)例子就是新建線程,有時(shí)候?yàn)榱耸∈驴寻穑瑫?huì)用下面的方法創(chuàng)建并啟動(dòng)一個(gè)線程匹颤,這是匿名內(nèi)部類的寫法印蓖,new Thread需要一個(gè) implements 自Runnable類型的對(duì)象實(shí)例作為參數(shù),比較好的方式是創(chuàng)建一個(gè)新類,這個(gè)類 implements Runnable勺爱,然后 new 出這個(gè)新類的實(shí)例作為參數(shù)傳給 Thread卫旱。而匿名內(nèi)部類不用找對(duì)象接收,直接當(dāng)做參數(shù)灸芳。
newThread(newRunnable() {? ? @Overridepublicvoidrun(){? ? ? ? System.out.println("快速新建并啟動(dòng)一個(gè)線程");? ? }}).run();
但是這樣寫是不是感覺(jué)看上去很亂蕊肥、很土,而這時(shí)候赔硫,換上 Lambda 表達(dá)式就是另外一種感覺(jué)了恼蓬。
newThread(()->{? ? System.out.println("快速新建并啟動(dòng)一個(gè)線程");}).run();
怎么樣,這樣一改,瞬間感覺(jué)清新脫俗了不少,簡(jiǎn)潔優(yōu)雅了不少。
Lambda 表達(dá)式簡(jiǎn)化了匿名內(nèi)部類的形式,可以達(dá)到同樣的效果若专,但是 Lambda 要優(yōu)雅的多。雖然最終達(dá)到的目的是一樣的,但其實(shí)內(nèi)部的實(shí)現(xiàn)原理卻不相同。
匿名內(nèi)部類在編譯之后會(huì)創(chuàng)建一個(gè)新的匿名內(nèi)部類出來(lái)爬早,而 Lambda 是調(diào)用 JVM invokedynamic指令實(shí)現(xiàn)的桨啃,并不會(huì)產(chǎn)生新類丧慈。
方法引用
方法引用的出現(xiàn)鹃愤,使得我們可以將一個(gè)方法賦給一個(gè)變量或者作為參數(shù)傳遞給另外一個(gè)方法关噪。::雙冒號(hào)作為方法引用的符號(hào)藤韵,比如下面這兩行語(yǔ)句匹涮,引用 Integer類的 parseInt方法雳攘。
Function s = Integer::parseInt;Integer i = s.apply("10");
或者下面這兩行吨灭,引用 Integer類的 compare方法浑彰。
Comparator comparator = Integer::compare;intresult = comparator.compare(100,10);
再比如,下面這兩行代碼撇他,同樣是引用 Integer類的 compare方法困肩,但是返回類型卻不一樣划纽,但卻都能正常執(zhí)行,并正確返回锌畸。
IntBinaryOperatorintBinaryOperator = Integer::compare;intresult = intBinaryOperator.applyAsInt(10,100);
相信有的同學(xué)看到這里恐怕是下面這個(gè)狀態(tài)勇劣,完全不可理喻嗎,也太隨便了吧,返回給誰(shuí)都能接盤比默。
先別激動(dòng)幻捏,來(lái)來(lái)來(lái),現(xiàn)在咱們就來(lái)解惑命咐,解除蒙圈臉篡九。
Q:什么樣的方法可以被引用?
A:這么說(shuō)吧醋奠,任何你有辦法訪問(wèn)到的方法都可以被引用榛臼。
Q:返回值到底是什么類型钝域?
A:這就問(wèn)到點(diǎn)兒上了讽坏,上面又是 Function、又是Comparator例证、又是 IntBinaryOperator的仙粱,看上去好像沒(méi)有規(guī)律竣蹦,其實(shí)不然。
返回的類型是 Java 8 專門定義的函數(shù)式接口,這類接口用 @FunctionalInterface 注解盗扇。
比如 Function這個(gè)函數(shù)式接口的定義如下:
@FunctionalInterfacepublicinterfaceFunction{Rapply(T t);}
還有很關(guān)鍵的一點(diǎn)肉盹,你的引用方法的參數(shù)個(gè)數(shù)肾档、類型聚唐,返回值類型要和函數(shù)式接口中的方法聲明一一對(duì)應(yīng)才行。
比如 Integer.parseInt方法定義如下:
publicstaticintparseInt(String s)throwsNumberFormatException{returnparseInt(s,10);}
首先parseInt方法的參數(shù)個(gè)數(shù)是 1 個(gè)捅位,而 Function中的 apply方法參數(shù)個(gè)數(shù)也是 1 個(gè)轧葛,參數(shù)個(gè)數(shù)對(duì)應(yīng)上了,再來(lái)艇搀,apply方法的參數(shù)類型和返回類型是泛型類型尿扯,所以肯定能和 parseInt方法對(duì)應(yīng)上。
這樣一來(lái)焰雕,就可以正確的接收Integer::parseInt的方法引用衷笋,并可以調(diào)用Funciton的apply方法,這時(shí)候矩屁,調(diào)用到的其實(shí)就是對(duì)應(yīng)的 Integer.parseInt方法了辟宗。
用這套標(biāo)準(zhǔn)套到 Integer::compare方法上,就不難理解為什么即可以用 Comparator<Integer>接收吝秕,又可以用 IntBinaryOperator接收了泊脐,而且調(diào)用它們各自的方法都能正確的返回結(jié)果。
Integer.compare方法定義如下:
publicstaticintcompare(intx,inty){return(x < y) ?-1: ((x == y) ?0:1);}
返回值類型 int烁峭,兩個(gè)參數(shù)晨抡,并且參數(shù)類型都是 int。
然后來(lái)看Comparator和IntBinaryOperator它們兩個(gè)的函數(shù)式接口定義和其中對(duì)應(yīng)的方法:
@FunctionalInterfacepublicinterfaceComparator{intcompare(T o1, T o2);}@FunctionalInterfacepublicinterfaceIntBinaryOperator{intapplyAsInt(intleft,intright);}
對(duì)不對(duì),都能正確的匹配上耘柱,所以前面示例中用這兩個(gè)函數(shù)式接口都能正常接收。其實(shí)不止這兩個(gè)棍现,只要是在某個(gè)函數(shù)式接口中聲明了這樣的方法:兩個(gè)參數(shù)调煎,參數(shù)類型是 int或者泛型,并且返回值是 int或者泛型的己肮,都可以完美接收士袄。
JDK 中定義了很多函數(shù)式接口,主要在 java.util.function包下谎僻,還有 java.util.Comparator 專門用作定制比較器娄柳。另外,前面說(shuō)的 Runnable也是一個(gè)函數(shù)式接口艘绍。
自己動(dòng)手實(shí)現(xiàn)一個(gè)例子
1. 定義一個(gè)函數(shù)式接口赤拒,并添加一個(gè)方法
定義了名稱為 KiteFunction 的函數(shù)式接口,使用 @FunctionalInterface注解诱鞠,然后聲明了具有兩個(gè)參數(shù)的方法 run挎挖,都是泛型類型,返回結(jié)果也是泛型航夺。
還有一點(diǎn)很重要蕉朵,函數(shù)式接口中只能聲明一個(gè)可被實(shí)現(xiàn)的方法,你不能聲明了一個(gè) run方法阳掐,又聲明一個(gè) start方法始衅,到時(shí)候編譯器就不知道用哪個(gè)接收了。而用default 關(guān)鍵字修飾的方法則沒(méi)有影響缭保。
@FunctionalInterfacepublicinterfaceKiteFunction{/**? ? * 定義一個(gè)雙參數(shù)的方法? ? *@paramt? ? *@params? ? *@return*/Rrun(T t,S s);}
2. 定義一個(gè)與 KiteFunction 中 run 方法對(duì)應(yīng)的方法
在 FunctionTest 類中定義了方法 DateFormat汛闸,一個(gè)將 LocalDateTime類型格式化為字符串類型的方法。
publicclassFunctionTest{publicstaticStringDateFormat(LocalDateTime dateTime, String partten){? ? ? ? DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(partten);returndateTime.format(dateTimeFormatter);? ? }}
3.用方法引用的方式調(diào)用
正常情況下我們直接使用 FunctionTest.DateFormat()就可以了涮俄。
而用函數(shù)式方式蛉拙,是這樣的。
KiteFunction functionDateFormat = FunctionTest::DateFormat;StringdateString = functionDateFormat.run(LocalDateTime.now(),"yyyy-MM-dd HH:mm:ss");
而其實(shí)我可以不專門在外面定義 DateFormat這個(gè)方法彻亲,而是像下面這樣孕锄,使用匿名內(nèi)部類。
publicstaticvoidmain(String[] args) throws Exception {StringdateString =newKiteFunction() {@OverridepublicStringrun(LocalDateTime localDateTime,Strings) {? ? ? ? ? ? DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(s);returnlocalDateTime.format(dateTimeFormatter);? ? ? ? }? ? }.run(LocalDateTime.now(),"yyyy-MM-dd HH:mm:ss");? ? System.out.println(dateString);}
前面第一個(gè) Runnable的例子也提到了苞尝,這樣的匿名內(nèi)部類可以用 Lambda 表達(dá)式的形式簡(jiǎn)寫畸肆,簡(jiǎn)寫后的代碼如下:
publicstaticvoidmain(String[] args) throws Exception {? ? ? ? KiteFunction functionDateFormat = (LocalDateTime dateTime,Stringpartten) -> {? ? ? ? ? ? DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(partten);returndateTime.format(dateTimeFormatter);? ? ? ? };StringdateString = functionDateFormat.run(LocalDateTime.now(),"yyyy-MM-dd HH:mm:ss");? ? ? ? System.out.println(dateString);}
使用(LocalDateTime dateTime, String partten) -> { } 這樣的 Lambda 表達(dá)式直接返回方法引用。
Stream API
為了說(shuō)一下 Stream API 的使用宙址,可以說(shuō)是大費(fèi)周章啊轴脐,知其然,也要知其所以然嗎,追求技術(shù)的態(tài)度和姿勢(shì)要正確大咱。
當(dāng)然 Stream 也不只是 Lambda 表達(dá)式就厲害了恬涧,真正厲害的還是它的功能,Stream 是 Java 8 中集合數(shù)據(jù)處理的利器碴巾,很多本來(lái)復(fù)雜溯捆、需要寫很多代碼的方法,比如過(guò)濾厦瓢、分組等操作提揍,往往使用 Stream 就可以在一行代碼搞定,當(dāng)然也因?yàn)?Stream 都是鏈?zhǔn)讲僮髦蟪穑恍写a可能會(huì)調(diào)用好幾個(gè)方法劳跃。
Collection接口提供了 stream()方法,讓我們可以在一個(gè)集合方便的使用 Stream API 來(lái)進(jìn)行各種操作浙垫。值得注意的是刨仑,我們執(zhí)行的任何操作都不會(huì)對(duì)源集合造成影響,你可以同時(shí)在一個(gè)集合上提取出多個(gè) stream 進(jìn)行操作绞呈。
我們看 Stream 接口的定義贸人,繼承自 BaseStream,機(jī)會(huì)所有的接口聲明都是接收方法引用類型的參數(shù)佃声,比如 filter方法艺智,接收了一個(gè) Predicate類型的參數(shù),它就是一個(gè)函數(shù)式接口圾亏,常用來(lái)作為條件比較十拣、篩選、過(guò)濾用志鹃,JPA中也使用了這個(gè)函數(shù)式接口用來(lái)做查詢條件拼接夭问。
publicinterfaceStreamextendsBaseStream>{Streamfilter(Predicate predicate);// 其他接口}?
下面就來(lái)看看 Stream 常用 API。
of
可接收一個(gè)泛型對(duì)象或可變成泛型集合曹铃,構(gòu)造一個(gè) Stream 對(duì)象缰趋。
privatestaticvoidcreateStream(){? ? Stream stringStream = Stream.of("a","b","c");}
empty
創(chuàng)建一個(gè)空的 Stream 對(duì)象。
concat
連接兩個(gè) Stream 陕见,不改變其中任何一個(gè) Steam 對(duì)象秘血,返回一個(gè)新的 Stream 對(duì)象。
privatestaticvoidconcatStream(){? ? Stream a = Stream.of("a","b","c");? ? Stream b = Stream.of("d","e");? ? Stream c = Stream.concat(a,b);}
max
一般用于求數(shù)字集合中的最大值评甜,或者按實(shí)體中數(shù)字類型的屬性比較灰粮,擁有最大值的那個(gè)實(shí)體。它接收一個(gè) Comparator<T>忍坷,上面也舉到這個(gè)例子了粘舟,它是一個(gè)函數(shù)式接口類型熔脂,專門用作定義兩個(gè)對(duì)象之間的比較,例如下面這個(gè)方法使用了 Integer::compareTo這個(gè)方法引用柑肴。
privatestaticvoidmax(){Stream integerStream =Stream.of(2,2,100,5);Integermax= integerStream.max(Integer::compareTo).get();System.out.println(max);}
當(dāng)然霞揉,我們也可以自己定制一個(gè) Comparator,順便復(fù)習(xí)一下 Lambda 表達(dá)式形式的方法引用嘉抒。
privatestaticvoidmax(){Stream integerStream =Stream.of(2,2,100,5);Comparator comparator =? (x, y) -> (x.intValue() < y.intValue()) ? -1: ((x.equals(y)) ?0:1);Integermax= integerStream.max(comparator).get();System.out.println(max);}
min
與 max 用法一樣零聚,只不過(guò)是求最小值。
findFirst
獲取 Stream 中的第一個(gè)元素些侍。
findAny
獲取 Stream 中的某個(gè)元素,如果是串行情況下政模,一般都會(huì)返回第一個(gè)元素岗宣,并行情況下就不一定了。
count
返回元素個(gè)數(shù)淋样。
Stream a = Stream.of("a","b","c");longx = a.count();
peek
建立一個(gè)通道耗式,在這個(gè)通道中對(duì) Stream 的每個(gè)元素執(zhí)行對(duì)應(yīng)的操作,對(duì)應(yīng) Consumer<T>的函數(shù)式接口趁猴,這是一個(gè)消費(fèi)者函數(shù)式接口刊咳,顧名思義,它是用來(lái)消費(fèi) Stream 元素的儡司,比如下面這個(gè)方法娱挨,把每個(gè)元素轉(zhuǎn)換成對(duì)應(yīng)的大寫字母并輸出。
privatestaticvoidpeek(){? ? Stream a = Stream.of("a","b","c");? ? Listlist= a.peek(e->System.out.println(e.toUpperCase())).collect(Collectors.toList());}
forEach
和 peek 方法類似捕犬,都接收一個(gè)消費(fèi)者函數(shù)式接口跷坝,可以對(duì)每個(gè)元素進(jìn)行對(duì)應(yīng)的操作,但是和 peek 不同的是碉碉,forEach 執(zhí)行之后柴钻,這個(gè) Stream 就真的被消費(fèi)掉了,之后這個(gè) Stream 流就沒(méi)有了垢粮,不可以再對(duì)它進(jìn)行后續(xù)操作了贴届,而 peek操作完之后,還是一個(gè)可操作的 Stream 對(duì)象蜡吧。
正好借著這個(gè)說(shuō)一下毫蚓,我們?cè)谑褂?Stream API 的時(shí)候,都是一串鏈?zhǔn)讲僮髡兜@是因?yàn)楹芏喾椒ㄉ苄热缃酉聛?lái)要說(shuō)到的 filter方法等,返回值還是這個(gè) Stream 類型的耀鸦,也就是被當(dāng)前方法處理過(guò)的 Stream 對(duì)象柬批,所以 Stream API 仍然可以使用啸澡。
privatestaticvoidforEach(){? ? Stream a = Stream.of("a","b","c");? ? a.forEach(e->System.out.println(e.toUpperCase()));}
forEachOrdered
功能與 forEach是一樣的,不同的是氮帐,forEachOrdered是有順序保證的嗅虏,也就是對(duì) Stream 中元素按插入時(shí)的順序進(jìn)行消費(fèi)。為什么這么說(shuō)呢上沐,當(dāng)開(kāi)啟并行的時(shí)候皮服,forEach和 forEachOrdered的效果就不一樣了。
Stream a = Stream.of("a","b","c");a.parallel().forEach(e->System.out.println(e.toUpperCase()));
當(dāng)使用上面的代碼時(shí)参咙,輸出的結(jié)果可能是 B龄广、A、C 或者 A蕴侧、C择同、B或者A、B净宵、C敲才,而使用下面的代碼,則每次都是 A择葡、 B紧武、C
Stream a = Stream.of("a","b","c");a.parallel().forEachOrdered(e->System.out.println(e.toUpperCase()));
limit
獲取前 n 條數(shù)據(jù),類似于 MySQL 的limit敏储,只不過(guò)只能接收一個(gè)參數(shù)阻星,就是數(shù)據(jù)條數(shù)。
privatestaticvoidlimit(){? ? Stream a = Stream.of("a","b","c");? ? a.limit(2).forEach(e->System.out.println(e));}
上述代碼打印的結(jié)果是 a虹曙、b迫横。
skip
跳過(guò)前 n 條數(shù)據(jù),例如下面代碼酝碳,返回結(jié)果是 c矾踱。
privatestaticvoidskip(){? ? Stream a = Stream.of("a","b","c");? ? a.skip(2).forEach(e->System.out.println(e));}
distinct
元素去重,例如下面方法返回元素是 a疏哗、b呛讲、c,將重復(fù)的 b 只保留了一個(gè)返奉。
privatestaticvoiddistinct(){? ? Stream a = Stream.of("a","b","c","b");? ? a.distinct().forEach(e->System.out.println(e));}
sorted
有兩個(gè)重載贝搁,一個(gè)無(wú)參數(shù),另外一個(gè)有個(gè) Comparator類型的參數(shù)芽偏。
無(wú)參類型的按照自然順序進(jìn)行排序雷逆,只適合比較單純的元素,比如數(shù)字污尉、字母等膀哲。
privatestaticvoidsorted(){? ? Stream a = Stream.of("a","c","b");? ? a.sorted().forEach(e->System.out.println(e));}
有參數(shù)的需要自定義排序規(guī)則往产,例如下面這個(gè)方法,按照第二個(gè)字母的大小順序排序某宪,最后輸出的結(jié)果是 a1仿村、b3、c6兴喂。
privatestaticvoidsortedWithComparator(){? ? Stream a = Stream.of("a1","c6","b3");? ? a.sorted((x,y)->Integer.parseInt(x.substring(1))>Integer.parseInt(y.substring(1))?1:-1).forEach(e->System.out.println(e));}
為了更好的說(shuō)明接下來(lái)的幾個(gè) API 蔼囊,我模擬了幾條項(xiàng)目中經(jīng)常用到的類似數(shù)據(jù),10條用戶信息衣迷。
privatestaticListgetUserData(){? ? Random random =newRandom();? ? List users =newArrayList<>();for(inti =1; i <=10; i++) {? ? ? ? User user =newUser();? ? ? ? user.setUserId(i);? ? ? ? user.setUserName(String.format("古時(shí)的風(fēng)箏 %s 號(hào)", i));? ? ? ? user.setAge(random.nextInt(100));? ? ? ? user.setGender(i %2);? ? ? ? user.setPhone("18812021111");? ? ? ? user.setAddress("無(wú)");? ? ? ? users.add(user);? ? }returnusers;}
filter
用于條件篩選過(guò)濾畏鼓,篩選出符合條件的數(shù)據(jù)。例如下面這個(gè)方法壶谒,篩選出性別為 0滴肿,年齡大于 50 的記錄佃迄。
privatestaticvoidfilter(){? ? List users = getUserData();? ? Stream stream = users.stream();? ? stream.filter(user -> user.getGender().equals(0) && user.getAge()>50).forEach(e->System.out.println(e));/**? ? *等同于下面這種形式 匿名內(nèi)部類? ? *///? ? stream.filter(new Predicate<User>() {//? ? ? ? @Override//? ? ? ? public boolean test(User user) {//? ? ? ? ? ? return user.getGender().equals(0) && user.getAge()>50;//? ? ? ? }//? ? }).forEach(e->System.out.println(e));}
map
map方法的接口方法聲明如下,接受一個(gè) Function函數(shù)式接口贵少,把它翻譯成映射最合適了呵俏,通過(guò)原始數(shù)據(jù)元素,映射出新的類型滔灶。
Streammap(Function<?super T, ? extends R> mapper);
而 Function的聲明是這樣的普碎,觀察 apply方法,接受一個(gè) T 型參數(shù)录平,返回一個(gè) R 型參數(shù)麻车。用于將一個(gè)類型轉(zhuǎn)換成另外一個(gè)類型正合適,這也是 map的初衷所在斗这,用于改變當(dāng)前元素的類型动猬,例如將 Integer 轉(zhuǎn)為 String類型,將 DAO 實(shí)體類型表箭,轉(zhuǎn)換為 DTO 實(shí)例類型赁咙。
當(dāng)然了,T 和 R 的類型也可以一樣免钻,這樣的話彼水,就和 peek方法沒(méi)什么不同了。
@FunctionalInterfacepublicinterfaceFunction{/**? ? * Applies this function to the given argument.? ? *? ? *@paramt the function argument? ? *@returnthe function result? ? */Rapply(T t);}
例如下面這個(gè)方法极舔,應(yīng)該是業(yè)務(wù)系統(tǒng)的常用需求凤覆,將 User 轉(zhuǎn)換為 API 輸出的數(shù)據(jù)格式。
privatestaticvoidmap(){? ? List users = getUserData();? ? Stream stream = users.stream();? ? List userDtos = stream.map(user -> dao2Dto(user)).collect(Collectors.toList());}privatestaticUserDtodao2Dto(User user){? ? UserDto dto =newUserDto();? ? BeanUtils.copyProperties(user, dto);//其他額外處理? ? return dto;}
mapToInt
將元素轉(zhuǎn)換成 int 類型拆魏,在 map方法的基礎(chǔ)上進(jìn)行封裝盯桦。
mapToLong
將元素轉(zhuǎn)換成 Long 類型慈俯,在 map方法的基礎(chǔ)上進(jìn)行封裝。
mapToDouble
將元素轉(zhuǎn)換成 Double 類型俺附,在 map方法的基礎(chǔ)上進(jìn)行封裝肥卡。
flatMap
這是用在一些比較特別的場(chǎng)景下,當(dāng)你的 Stream 是以下這幾種結(jié)構(gòu)的時(shí)候事镣,需要用到 flatMap方法步鉴,用于將原有二維結(jié)構(gòu)扁平化。
Stream<String[]>
Stream<Set<String>>
Stream<List<String>>
以上這三類結(jié)構(gòu)璃哟,通過(guò) flatMap方法氛琢,可以將結(jié)果轉(zhuǎn)化為 Stream<String>這種形式,方便之后的其他操作随闪。
比如下面這個(gè)方法阳似,將List<List<User>>扁平處理,然后再使用 map或其他方法進(jìn)行操作铐伴。
privatestaticvoid flatMap(){List users = getUserData();List users1 = getUserData();List> userList =newArrayList<>();? ? userList.add(users);? ? userList.add(users1);? ? Stream> stream = userList.stream();List userDtos = stream.flatMap(subUserList->subUserList.stream()).map(user -> dao2Dto(user)).collect(Collectors.toList());}
flatMapToInt
用法參考 flatMap撮奏,將元素扁平為 int 類型,在 flatMap方法的基礎(chǔ)上進(jìn)行封裝当宴。
flatMapToLong
用法參考 flatMap畜吊,將元素扁平為 Long 類型,在 flatMap方法的基礎(chǔ)上進(jìn)行封裝户矢。
flatMapToDouble
用法參考 flatMap玲献,將元素扁平為 Double 類型,在 flatMap方法的基礎(chǔ)上進(jìn)行封裝梯浪。
collection
在進(jìn)行了一系列操作之后捌年,我們最終的結(jié)果大多數(shù)時(shí)候并不是為了獲取 Stream 類型的數(shù)據(jù),而是要把結(jié)果變?yōu)?List挂洛、Map 這樣的常用數(shù)據(jù)結(jié)構(gòu)礼预,而 collection就是為了實(shí)現(xiàn)這個(gè)目的。
就拿 map 方法的那個(gè)例子說(shuō)明抹锄,將對(duì)象類型進(jìn)行轉(zhuǎn)換后逆瑞,最終我們需要的結(jié)果集是一個(gè) List<UserDto >類型的,使用 collect方法將 Stream 轉(zhuǎn)換為我們需要的類型伙单。
下面是 collect接口方法的定義:
Rcollect(Collector collector);
下面這個(gè)例子演示了將一個(gè)簡(jiǎn)單的 Integer Stream 過(guò)濾出大于 7 的值获高,然后轉(zhuǎn)換成 List<Integer>集合,用的是 Collectors.toList()這個(gè)收集器吻育。
privatestaticvoidcollect(){? ? Stream integerStream = Stream.of(1,2,5,7,8,12,33);? ? Listlist= integerStream.filter(s -> s.intValue()>7).collect(Collectors.toList());}
很多同學(xué)表示看不太懂這個(gè) Collector是怎么一個(gè)意思念秧,來(lái),我們看下面這段代碼布疼,這是 collect的另一個(gè)重載方法摊趾,你可以理解為它的參數(shù)是按順序執(zhí)行的币狠,這樣就清楚了,這就是個(gè) ArrayList 從創(chuàng)建到調(diào)用 addAll方法的一個(gè)過(guò)程砾层。
privatestaticvoid collect(){? ? Stream integerStream = Stream.of(1,2,5,7,8,12,33);Listlist= integerStream.filter(s -> s.intValue()>7).collect(ArrayList::new, ArrayList::add,? ? ? ? ? ? ArrayList::addAll);}
我們?cè)谧远x Collector的時(shí)候其實(shí)也是這個(gè)邏輯漩绵,不過(guò)我們根本不用自定義, Collectors已經(jīng)為我們提供了很多拿來(lái)即用的收集器肛炮。比如我們經(jīng)常用到Collectors.toList()止吐、Collectors.toSet()、Collectors.toMap()侨糟。另外還有比如Collectors.groupingBy()用來(lái)分組碍扔,比如下面這個(gè)例子,按照 userId 字段分組秕重,返回以 userId 為key不同,List 為value 的 Map,或者返回每個(gè) key 的個(gè)數(shù)溶耘。
// 返回 userId:ListMap>map= user.stream().collect(Collectors.groupingBy(User::getUserId));//返回 userId:每組個(gè)數(shù)Mapmap= user.stream().collect(Collectors.groupingBy(User::getUserId,Collectors.counting()));
toArray
collection是返回列表二拐、map 等,toArray是返回?cái)?shù)組凳兵,有兩個(gè)重載卓鹿,一個(gè)空參數(shù),返回的是 Object[]留荔。
另一個(gè)接收一個(gè) IntFunction<R>類型參數(shù)。
@FunctionalInterfacepublicinterfaceIntFunction{/**? ? * Applies this function to the given argument.? ? *? ? *@paramvalue the function argument? ? *@returnthe function result? ? */Rapply(intvalue);}
比如像下面這樣使用澜倦,參數(shù)是 User[]::new也就是new 一個(gè) User 數(shù)組聚蝶,長(zhǎng)度為最后的 Stream 長(zhǎng)度。
privatestaticvoidtoArray(){? ? List users = getUserData();? ? Stream stream = users.stream();? ? User[] userArray = stream.filter(user -> user.getGender().equals(0) && user.getAge() >50).toArray(User[]::new);}
reduce
它的作用是每次計(jì)算的時(shí)候都用到上一次的計(jì)算結(jié)果藻治,比如求和操作碘勉,前兩個(gè)數(shù)的和加上第三個(gè)數(shù)的和,再加上第四個(gè)數(shù)桩卵,一直加到最后一個(gè)數(shù)位置验靡,最后返回結(jié)果,就是 reduce的工作過(guò)程雏节。
privatestaticvoidreduce(){? ? Stream integerStream = Stream.of(1,2,5,7,8,12,33);? ? Integer sum = integerStream.reduce(0,(x,y)->x+y);? ? System.out.println(sum);}
另外 Collectors好多方法都用到了 reduce胜嗓,比如 groupingBy、minBy钩乍、maxBy等等辞州。
并行 Stream
Stream 本質(zhì)上來(lái)說(shuō)就是用來(lái)做數(shù)據(jù)處理的,為了加快處理速度寥粹,Stream API 提供了并行處理 Stream 的方式变过。通過(guò) users.parallelStream()或者users.stream().parallel() 的方式來(lái)創(chuàng)建并行 Stream 對(duì)象埃元,支持的 API 和普通 Stream 幾乎是一致的。
并行 Stream 默認(rèn)使用 ForkJoinPool線程池媚狰,當(dāng)然也支持自定義岛杀,不過(guò)一般情況下沒(méi)有必要。ForkJoin 框架的分治策略與并行流處理正好契合崭孤。
雖然并行這個(gè)詞聽(tīng)上去很厲害类嗤,但并不是所有情況使用并行流都是正確的,很多時(shí)候完全沒(méi)這個(gè)必要裳瘪。
什么情況下使用或不應(yīng)使用并行流操作呢土浸?
必須在多核 CPU 下才使用并行 Stream,聽(tīng)上去好像是廢話彭羹。
在數(shù)據(jù)量不大的情況下使用普通串行 Stream 就可以了黄伊,使用并行 Stream 對(duì)性能影響不大。
CPU 密集型計(jì)算適合使用并行 Stream派殷,而 IO 密集型使用并行 Stream 反而會(huì)更慢还最。
雖然計(jì)算是并行的可能很快,但最后大多數(shù)時(shí)候還是要使用 collect合并的毡惜,如果合并代價(jià)很大拓轻,也不適合用并行 Stream。
有些操作经伙,比如 limit扶叉、 findFirst、forEachOrdered 等依賴于元素順序的操作帕膜,都不適合用并行 Stream枣氧。
最后
Java 25 周歲了,有多少同學(xué)跟我一樣在用 Java 8垮刹,還有多少同學(xué)在用更早的版本达吞,請(qǐng)說(shuō)出你的故事。