8000字長(zhǎng)文讓你搞懂Java8的Lambda丸逸、函數(shù)式接口蹋艺、Stream用法 原理

就在今年 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ō)出你的故事。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荒典,一起剝皮案震驚了整個(gè)濱河市酪劫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寺董,老刑警劉巖覆糟,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異遮咖,居然都是意外死亡搪桂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)踢械,“玉大人酗电,你說(shuō)我怎么就攤上這事∧诹校” “怎么了撵术?”我有些...
    開(kāi)封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)话瞧。 經(jīng)常有香客問(wèn)我嫩与,道長(zhǎng),這世上最難降的妖魔是什么交排? 我笑而不...
    開(kāi)封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任划滋,我火速辦了婚禮,結(jié)果婚禮上埃篓,老公的妹妹穿的比我還像新娘处坪。我一直安慰自己,他們只是感情好架专,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布同窘。 她就那樣靜靜地躺著,像睡著了一般部脚。 火紅的嫁衣襯著肌膚如雪想邦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天委刘,我揣著相機(jī)與錄音丧没,去河邊找鬼。 笑死锡移,一個(gè)胖子當(dāng)著我的面吹牛骂铁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罩抗,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼灿椅!你這毒婦竟也來(lái)了套蒂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤茫蛹,失蹤者是張志新(化名)和其女友劉穎操刀,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體婴洼,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骨坑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欢唾。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡且警,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出礁遣,到底是詐尸還是另有隱情斑芜,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布祟霍,位于F島的核電站杏头,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沸呐。R本人自食惡果不足惜醇王,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望崭添。 院中可真熱鬧寓娩,春花似錦、人聲如沸滥朱。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)徙邻。三九已至排嫌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缰犁,已是汗流浹背淳地。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帅容,地道東北人颇象。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像并徘,于是被迫代替她去往敵國(guó)和親遣钳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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