萬(wàn)字文讓你了解 Java 8 Lambda蝙场、函數(shù)式接口、Stream 用法和原理

一定要看到最后粱年,那是心動(dòng)的感覺售滤!

我是風(fēng)箏,公眾號(hào)「古時(shí)的風(fēng)箏」台诗。一個(gè)兼具深度與廣度的程序員鼓勵(lì)師完箩,一個(gè)本打算寫詩(shī)卻寫起了代碼的田園碼農(nóng)拉队!文章會(huì)收錄在 JavaNewBee 中弊知,更有 Java 后端知識(shí)圖譜,從小白到大牛要走的路都在里面粱快。公眾號(hào)回復(fù)『666』獲取高清大圖秩彤。

就在今年 Java 25周歲了,可能比在座的各位中的一些少年年齡還大事哭,但令人遺憾的是漫雷,竟然沒有我大,不禁感嘆慷蠕,Java 還是太小了珊拼。(難道我會(huì)說(shuō)是因?yàn)槲依狭耍浚?/p>

而就在上個(gè)月流炕,Java 15 的試驗(yàn)版悄悄發(fā)布了澎现,但是在 Java 界一直有個(gè)神秘現(xiàn)象,那就是「你發(fā)你發(fā)任你發(fā)每辟,我的最愛 Java 8」.

據(jù) Snyk 和 The Java Magazine 聯(lián)合推出發(fā)布的 2020 JVM 生態(tài)調(diào)查報(bào)告顯示剑辫,在所有的 Java 版本中,仍然有 64% 的開發(fā)者使用 Java 8渠欺。另外一些開發(fā)者可能已經(jīng)開始用 Java 9妹蔽、Java 11、Java 13 了,當(dāng)然還有一些神仙開發(fā)者還在堅(jiān)持使用 JDK 1.6 和 1.7胳岂。

盡管 Java 8 發(fā)布多年编整,使用者眾多,可神奇的是竟然有很多同學(xué)沒有用過 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 很方便剑勾,沒錯(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ù),即沒有函數(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;

}

最常見的一個(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() {

@Override

publicvoidrun(){

System.out.println("快速新建并啟動(dòng)一個(gè)線程");

}

}).run();

但是這樣寫是不是感覺看上去很亂、很土访敌,而這時(shí)候凉敲,換上 Lambda 表達(dá)式就是另外一種感覺了。

newThread(()->{

System.out.println("快速新建并啟動(dòng)一個(gè)線程");

}).run();

怎么樣寺旺,這樣一改,瞬間感覺清新脫俗了不少势决,簡(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ō)吧,任何你有辦法訪問到的方法都可以被引用脾歇。

Q:返回值到底是什么類型?

A:這就問到點(diǎn)兒上了藕各,上面又是 Function池摧、又是Comparator、又是 IntBinaryOperator的激况,看上去好像沒有規(guī)律作彤,其實(shí)不然乌逐。

返回的類型是 Java 8 專門定義的函數(shù)式接口,這類接口用 @FunctionalInterface 注解浙踢。

比如 Function這個(gè)函數(shù)式接口的定義如下:

@FunctionalInterface

publicinterfaceFunction{

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)的方法:

@FunctionalInterface

publicinterfaceComparator{

intcompare(T o1, T o2);

}

@FunctionalInterface

publicinterfaceIntBinaryOperator{

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)鍵字修飾的方法則沒有影響心包。

@FunctionalInterface

publicinterfaceKiteFunction{

/**

? ? * 定義一個(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() {

@Override

publicStringrun(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ù)雜憔购、需要寫很多代碼的方法岔帽,比如過濾、分組等操作山卦,往往使用 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)作為條件比較、篩選顾患、過濾用,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 用法一樣,只不過是求最小值像寒。

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 流就沒有了宫纬,不可以再對(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)前方法處理過的 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)開啟并行的時(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澳泵,只不過只能接收一個(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

跳過前 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

用于條件篩選過濾懂从,篩選出符合條件的數(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ù)式接口,把它翻譯成映射最合適了乞娄,通過原始數(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方法沒什么不同了。

@FunctionalInterface

publicinterfaceFunction{

/**

? ? * 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)祖娘,通過 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 過濾出大于 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è)過程蜕煌。

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è)邏輯,不過我們根本不用自定義斜纪, 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

Map>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ù)。

@FunctionalInterface

publicinterfaceIntFunction{

/**

? ? * 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的工作過程。

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 的方式。通過 users.parallelStream()或者users.stream().parallel() 的方式來(lái)創(chuàng)建并行 Stream 對(duì)象华畏,支持的 API 和普通 Stream 幾乎是一致的尊蚁。

并行 Stream 默認(rèn)使用 ForkJoinPool線程池,當(dāng)然也支持自定義横朋,不過一般情況下沒有必要。ForkJoin 框架的分治策略與并行流處理正好契合晰甚。

雖然并行這個(gè)詞聽上去很厲害,但并不是所有情況使用并行流都是正確的厕九,很多時(shí)候完全沒這個(gè)必要。

什么情況下使用或不應(yīng)使用并行流操作呢俊鱼?

必須在多核 CPU 下才使用并行 Stream畅买,聽上去好像是廢話。

在數(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ō)出你的故事。現(xiàn)在整個(gè)社會(huì)都是IT引領(lǐng)者潮流,改變著時(shí)代跪者,說(shuō)句更實(shí)在的話棵帽,身為程序員的你,是p6坑夯?p7?如果都不是還不著急那就是你的錯(cuò)了岖寞。

小編在這里給你大家一份福利,大大的福利柜蜈,如果正在處于這個(gè)階段仗谆,那就添加w?:bjmsb07 來(lái)咨詢?cè)斍榘桑屇銓W(xué)好p6淑履,p7,還免費(fèi)送給你p8秘噪!想拿到高薪狸吞,想年薪百萬(wàn),對(duì)于程序員來(lái)說(shuō)指煎,不是什么難處蹋偏,看你下不下功夫了!(不信你可以驗(yàn)證一下至壤,反正對(duì)你也沒有什么損失)

壯士且慢威始,先給點(diǎn)個(gè)贊吧,總是被白嫖像街,身體吃不消黎棠!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市镰绎,隨后出現(xiàn)的幾起案子脓斩,更是在濱河造成了極大的恐慌,老刑警劉巖畴栖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件随静,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吗讶,警方通過查閱死者的電腦和手機(jī)挪挤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)关翎,“玉大人扛门,你說(shuō)我怎么就攤上這事∽萸蓿” “怎么了论寨?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵星立,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我葬凳,道長(zhǎng)绰垂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任火焰,我火速辦了婚禮劲装,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昌简。我一直安慰自己占业,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布纯赎。 她就那樣靜靜地躺著谦疾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪犬金。 梳的紋絲不亂的頭發(fā)上念恍,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音晚顷,去河邊找鬼峰伙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛该默,可吹牛的內(nèi)容都是我干的瞳氓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼权均,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了锅锨?” 一聲冷哼從身側(cè)響起叽赊,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎必搞,沒想到半個(gè)月后必指,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恕洲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年塔橡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霜第。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡葛家,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泌类,到底是詐尸還是另有隱情癞谒,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站弹砚,受9級(jí)特大地震影響双仍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桌吃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一朱沃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茅诱,春花似錦逗物、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至尔当,卻和暖如春莲祸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背椭迎。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工锐帜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人畜号。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓缴阎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親简软。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛮拔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348