一文帶你徹底了解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"); private?int?plus(int?a,?int?b){ ???return?a+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ù)。
new?Thread(new?Runnable()?{ ????@Override ????public?void?run()?{ ????????System.out.println("快速新建并啟動(dòng)一個(gè)線程"); ????} }).run();
但是這樣寫是不是感覺(jué)看上去很亂、很土,而這時(shí)候,換上 Lambda 表達(dá)式就是另外一種感覺(jué)了。
new?Thread(()->{ ????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<String,?Integer>?s?=?Integer::parseInt; Integer?i?=?s.apply("10");
或者下面這兩行,引用 Integer
類的 compare
方法。
Comparator<Integer>?comparator?=?Integer::compare; int?result?=?comparator.compare(100,10);
再比如,下面這兩行代碼,同樣是引用 Integer
類的 compare
方法,但是返回類型卻不一樣,但卻都能正常執(zhí)行,并正確返回。
IntBinaryOperator?intBinaryOperator?=?Integer::compare; int?result?=?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ù)式接口的定義如下:
@FunctionalInterface public?interface?Function<T,?R>?{ ????R?apply(T?t); }
還有很關(guān)鍵的一點(diǎn),你的引用方法的參數(shù)個(gè)數(shù)、類型,返回值類型要和函數(shù)式接口中的方法聲明一一對(duì)應(yīng)才行。
比如 Integer.parseInt
方法定義如下:
public?static?int?parseInt(String?s)?throws?NumberFormatException?{ ????return?parseInt(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
方法定義如下:
public?static?int?compare(int?x,?int?y)?{ ????return?(x?<?y)???-1?:?((x?==?y)???0?:?1); }
返回值類型 int
,兩個(gè)參數(shù),并且參數(shù)類型都是 int
。
然后來(lái)看Comparator
和IntBinaryOperator
它們兩個(gè)的函數(shù)式接口定義和其中對(duì)應(yīng)的方法:
@FunctionalInterface public?interface?Comparator<T>?{ ????int?compare(T?o1,?T?o2); } @FunctionalInterface public?interface?IntBinaryOperator?{ ????int?applyAsInt(int?left,?int?right); }
對(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)有影響。
@FunctionalInterface public?interface?KiteFunction<T,?R,?S>?{ ????/** ?????*?定義一個(gè)雙參數(shù)的方法 ?????*?@param?t ?????*?@param?s ?????*?@return ?????*/ ????R?run(T?t,S?s); }
2. 定義一個(gè)與 KiteFunction 中 run 方法對(duì)應(yīng)的方法
在 FunctionTest 類中定義了方法 DateFormat
,一個(gè)將 LocalDateTime
類型格式化為字符串類型的方法。
public?class?FunctionTest?{ ????public?static?String?DateFormat(LocalDateTime?dateTime,?String?partten)?{ ????????DateTimeFormatter?dateTimeFormatter?=?DateTimeFormatter.ofPattern(partten); ????????return?dateTime.format(dateTimeFormatter); ????} }
3.用方法引用的方式調(diào)用
正常情況下我們直接使用 FunctionTest.DateFormat()
就可以了。
而用函數(shù)式方式,是這樣的。
KiteFunction<LocalDateTime,String,String>?functionDateFormat?=?FunctionTest::DateFormat; String?dateString?=?functionDateFormat.run(LocalDateTime.now(),"yyyy-MM-dd?HH:mm:ss");
而其實(shí)我可以不專門在外面定義 DateFormat
這個(gè)方法,而是像下面這樣,使用匿名內(nèi)部類。
public?static?void?main(String[]?args)?throws?Exception?{ ????String?dateString?=?new?KiteFunction<LocalDateTime,?String,?String>()?{ ????????@Override ????????public?String?run(LocalDateTime?localDateTime,?String?s)?{ ????????????DateTimeFormatter?dateTimeFormatter?=?DateTimeFormatter.ofPattern(s); ????????????return?localDateTime.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)寫后的代碼如下:
public?static?void?main(String[]?args)?throws?Exception?{ ????????KiteFunction<LocalDateTime,?String,?String>?functionDateFormat?=?(LocalDateTime?dateTime,?String?partten)?->?{ ????????????DateTimeFormatter?dateTimeFormatter?=?DateTimeFormatter.ofPattern(partten); ????????????return?dateTime.format(dateTimeFormatter); ????????}; ????????String?dateString?=?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)讲僮?,一行代碼可能會(huì)調(diào)用好幾個(gè)方法。
Collection
接口提供了 stream()
方法,讓我們可以在一個(gè)集合方便的使用 Stream API 來(lái)進(jìn)行各種操作。值得注意的是,我們執(zhí)行的任何操作都不會(huì)對(duì)源集合造成影響,你可以同時(shí)在一個(gè)集合上提取出多個(gè) stream 進(jìn)行操作。
我們看 Stream 接口的定義,繼承自 BaseStream
,幾乎所有的接口聲明都是接收方法引用類型的參數(shù),比如 filter
方法,接收了一個(gè) Predicate
類型的參數(shù),它就是一個(gè)函數(shù)式接口,常用來(lái)作為條件比較、篩選、過(guò)濾用,JPA
中也使用了這個(gè)函數(shù)式接口用來(lái)做查詢條件拼接。
public?interface?Stream<T>?extends?BaseStream<T,?Stream<T>>?{ ??Stream<T>?filter(Predicate<??super?T>?predicate); ??//?其他接口 }??
下面就來(lái)看看 Stream 常用 API。
of
可接收一個(gè)泛型對(duì)象或可變成泛型集合,構(gòu)造一個(gè) Stream 對(duì)象。
private?static?void?createStream(){ ????Stream<String>?stringStream?=?Stream.of("a","b","c"); }
empty
創(chuàng)建一個(gè)空的 Stream 對(duì)象。
concat
連接兩個(gè) Stream ,不改變其中任何一個(gè) Steam 對(duì)象,返回一個(gè)新的 Stream 對(duì)象。
private?static?void?concatStream(){ ????Stream<String>?a?=?Stream.of("a","b","c"); ????Stream<String>?b?=?Stream.of("d","e"); ????Stream<String>?c?=?Stream.concat(a,b); }
max
一般用于求數(shù)字集合中的最大值,或者按實(shí)體中數(shù)字類型的屬性比較,擁有最大值的那個(gè)實(shí)體。它接收一個(gè) Comparator<T>
,上面也舉到這個(gè)例子了,它是一個(gè)函數(shù)式接口類型,專門用作定義兩個(gè)對(duì)象之間的比較,例如下面這個(gè)方法使用了 Integer::compareTo
這個(gè)方法引用。
private?static?void?max(){ ????Stream<Integer>?integerStream?=?Stream.of(2,?2,?100,?5); ????Integer?max?=?integerStream.max(Integer::compareTo).get(); ????System.out.println(max); }
當(dāng)然,我們也可以自己定制一個(gè) Comparator
,順便復(fù)習(xí)一下 Lambda 表達(dá)式形式的方法引用。
private?static?void?max(){ ????Stream<Integer>?integerStream?=?Stream.of(2,?2,?100,?5); ????Comparator<Integer>?comparator?=??(x,?y)?->?(x.intValue()?<?y.intValue())???-1?:?((x.equals(y))???0?:?1); ????Integer?max?=?integerStream.max(comparator).get(); ????System.out.println(max); }
min
與 max 用法一樣,只不過(guò)是求最小值。
findFirst
獲取 Stream 中的第一個(gè)元素。
findAny
獲取 Stream 中的某個(gè)元素,如果是串行情況下,一般都會(huì)返回第一個(gè)元素,并行情況下就不一定了。
count
返回元素個(gè)數(shù)。
Stream<String>?a?=?Stream.of("a",?"b",?"c"); long?x?=?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)的大寫字母并輸出。
private?static?void?peek()?{ ????Stream<String>?a?=?Stream.of("a",?"b",?"c"); ????List<String>?list?=?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 仍然可以使用。
private?static?void?forEach()?{ ????Stream<String>?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<String>?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<String>?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ù)。
private?static?void?limit()?{ ????Stream<String>?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。
private?static?void?skip()?{ ????Stream<String>?a?=?Stream.of("a",?"b",?"c"); ????a.skip(2).forEach(e->System.out.println(e)); }
distinct
元素去重,例如下面方法返回元素是 a、b、c,將重復(fù)的 b 只保留了一個(gè)。
private?static?void?distinct()?{ ????Stream<String>?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ù)字、字母等。
private?static?void?sorted()?{ ????Stream<String>?a?=?Stream.of("a",?"c",?"b"); ????a.sorted().forEach(e->System.out.println(e)); }
有參數(shù)的需要自定義排序規(guī)則,例如下面這個(gè)方法,按照第二個(gè)字母的大小順序排序,最后輸出的結(jié)果是 a1、b3、c6。
private?static?void?sortedWithComparator()?{ ????Stream<String>?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條用戶信息。
private?static?List<User>?getUserData()?{ ????Random?random?=?new?Random(); ????List<User>?users?=?new?ArrayList<>(); ????for?(int?i?=?1;?i?<=?10;?i++)?{ ????????User?user?=?new?User(); ????????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); ????} ????return?users; }
filter
用于條件篩選過(guò)濾,篩選出符合條件的數(shù)據(jù)。例如下面這個(gè)方法,篩選出性別為 0,年齡大于 50 的記錄。
private?static?void?filter(){ ????List<User>?users?=?getUserData(); ????Stream<User>?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ù)元素,映射出新的類型。
<R>?Stream<R>?map(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)什么不同了。
@FunctionalInterface public?interface?Function<T,?R>?{ ????/** ?????*?Applies?this?function?to?the?given?argument. ?????* ?????*?@param?t?the?function?argument ?????*?@return?the?function?result ?????*/ ????R?apply(T?t); }
例如下面這個(gè)方法,應(yīng)該是業(yè)務(wù)系統(tǒng)的常用需求,將 User 轉(zhuǎn)換為 API 輸出的數(shù)據(jù)格式。
private?static?void?map(){ ????List<User>?users?=?getUserData(); ????Stream<User>?stream?=?users.stream(); ????List<UserDto>?userDtos?=?stream.map(user?->?dao2Dto(user)).collect(Collectors.toList()); } private?static?UserDto?dao2Dto(User?user){ ????UserDto?dto?=?new?UserDto(); ????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)行操作。
private?static?void?flatMap(){ ????List<User>?users?=?getUserData(); ????List<User>?users1?=?getUserData(); ????List<List<User>>?userList?=?new?ArrayList<>(); ????userList.add(users); ????userList.add(users1); ????Stream<List<User>>?stream?=?userList.stream(); ????List<UserDto>?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
接口方法的定義:
<R,?A>?R?collect(Collector<??super?T,?A,?R>?collector);
下面這個(gè)例子演示了將一個(gè)簡(jiǎn)單的 Integer Stream 過(guò)濾出大于 7 的值,然后轉(zhuǎn)換成 List<Integer>
集合,用的是 Collectors.toList()
這個(gè)收集器。
private?static?void?collect(){ ????Stream<Integer>?integerStream?=?Stream.of(1,2,5,7,8,12,33); ????List<Integer>?list?=?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ò)程。
private?static?void?collect(){ ????Stream<Integer>?integerStream?=?Stream.of(1,2,5,7,8,12,33); ????List<Integer>?list?=?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:List<User> Map<String,List<User>>?map?=?user.stream().collect(Collectors.groupingBy(User::getUserId)); //?返回?userId:每組個(gè)數(shù) Map<String,Long>?map?=?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ù)。
@FunctionalInterface public?interface?IntFunction<R>?{ ????/** ?????*?Applies?this?function?to?the?given?argument. ?????* ?????*?@param?value?the?function?argument ?????*?@return?the?function?result ?????*/ ????R?apply(int?value); }
比如像下面這樣使用,參數(shù)是 User[]::new
也就是new 一個(gè) User 數(shù)組,長(zhǎng)度為最后的 Stream 長(zhǎng)度。
private?static?void?toArray()?{ ????List<User>?users?=?getUserData(); ????Stream<User>?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ò)程。
private?static?void?reduce(){ ????Stream<Integer>?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。
以上就是一文帶你徹底了解Java8中的Lambda,函數(shù)式接口和Stream的詳細(xì)內(nèi)容,更多關(guān)于Java8 Lambda 函數(shù)式接口 Stream的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java的Lambda表達(dá)式和Stream流的作用以及示例
- Java分析Lambda表達(dá)式Stream流合并分組內(nèi)對(duì)象數(shù)據(jù)合并
- Java中的lambda和stream實(shí)現(xiàn)排序
- Java詳細(xì)分析Lambda表達(dá)式與Stream流的使用方法
- 吊打Java面試官之Lambda表達(dá)式 Stream API
- Java8中Lambda表達(dá)式使用和Stream API詳解
- 詳解Java遞歸實(shí)現(xiàn)樹(shù)形結(jié)構(gòu)的兩種方式
- Java實(shí)現(xiàn)樹(shù)形結(jié)構(gòu)的示例代碼
- Java樹(shù)形結(jié)構(gòu)數(shù)據(jù)生成導(dǎo)出excel文件方法記錄
- Java使用 Stream 流和 Lambda 組裝復(fù)雜父子樹(shù)形結(jié)構(gòu)
相關(guān)文章
Java多線程死鎖問(wèn)題詳解(wait和notify)
線程之間形成相互等待資源的環(huán)時(shí),就會(huì)形成順序死鎖,下面這篇文章主要給大家介紹了關(guān)于Java多線程死鎖問(wèn)題(wait和notify)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01Spring實(shí)現(xiàn)類私有方法的幾個(gè)問(wèn)題(親測(cè)通用解決方案)
現(xiàn)實(shí)的業(yè)務(wù)場(chǎng)景中,可能需要對(duì)Spring的實(shí)現(xiàn)類的私有方法進(jìn)行測(cè)試。本文給大家分享Spring實(shí)現(xiàn)類私有方法面臨的幾個(gè)問(wèn)題及解決方案,感興趣的朋友跟隨小編一起看看吧2021-06-06ApiOperation和ApiParam注解依賴的安裝和使用以及注意事項(xiàng)說(shuō)明
這篇文章主要介紹了ApiOperation和ApiParam注解依賴的安裝和使用以及注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09java實(shí)現(xiàn)簡(jiǎn)單的汽車租賃系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單的汽車租賃系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02如何通過(guò)idea實(shí)現(xiàn)springboot集成mybatis
這篇文章主要介紹了如何通過(guò)idea實(shí)現(xiàn)springboot集成mybatis,使用springboot 集成 mybatis后,通過(guò)http請(qǐng)求接口,使得通過(guò)http請(qǐng)求可以直接操作數(shù)據(jù)庫(kù),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09Java如何優(yōu)雅關(guān)閉異步中的ExecutorService
在并發(fā)編程領(lǐng)域,Java的ExecutorService是線程池管理的關(guān)鍵接口,這篇文章主要為大家介紹了如何優(yōu)雅關(guān)閉異步中的ExecutorService,需要的可以了解下2025-02-02