一文帶你徹底了解Java8中的Lambda,函數(shù)式接口和Stream
就在今年 Java 25周歲了,可能比在座的各位中的一些少年年齡還大,但令人遺憾的是,竟然沒有我大,不禁感嘆,Java 還是太小了。(難道我會說是因為我老了?)
而就在上個月,Java 15 的試驗版悄悄發(fā)布了,但是在 Java 界一直有個神秘現(xiàn)象,那就是「你發(fā)你發(fā)任你發(fā),我的最愛 Java 8」.
據(jù) Snyk 和 The Java Magazine 聯(lián)合推出發(fā)布的 2020 JVM 生態(tài)調(diào)查報告顯示,在所有的 Java 版本中,仍然有 64% 的開發(fā)者使用 Java 8。另外一些開發(fā)者可能已經(jīng)開始用 Java 9、Java 11、Java 13 了,當然還有一些神仙開發(fā)者還在堅持使用 JDK 1.6 和 1.7。
盡管 Java 8 發(fā)布多年,使用者眾多,可神奇的是竟然有很多同學沒有用過 Java 8 的新特性,比如 Lambda表達式、比如方法引用,再比如今天要說的 Stream。其實 Stream 就是以 Lambda 和方法引用為基礎(chǔ),封裝的簡單易用、函數(shù)式風格的 API。
Java 8 是在 2014 年發(fā)布的,實話說,風箏我也是在 Java 8 發(fā)布后很長一段時間才用的 Stream,因為 Java 8 發(fā)布的時候我還在 C# 的世界中掙扎,而使用 Lambda 表達式卻很早了,因為 Python 中用 Lambda 很方便,沒錯,我寫 Python 的時間要比 Java 的時間還長。
要講 Stream ,那就不得不先說一下它的左膀右臂 Lambda 和方法引用,你用的 Stream API 其實就是函數(shù)式的編程風格,其中的「函數(shù)」就是方法引用,「式」就是 Lambda 表達式。
Lambda 表達式
Lambda 表達式是一個匿名函數(shù),Lambda表達式基于數(shù)學中的λ演算得名,直接對應(yīng)于其中的lambda抽象,是一個匿名函數(shù),即沒有函數(shù)名的函數(shù)。Lambda表達式可以表示閉包。
在 Java 中,Lambda 表達式的格式是像下面這樣
//?無參數(shù),無返回值 ()?->?log.info("Lambda") ?//?有參數(shù),有返回值 (int?a,?int?b)?->?{?a+b?}
其等價于
log.info("Lambda"); private?int?plus(int?a,?int?b){ ???return?a+b; }
最常見的一個例子就是新建線程,有時候為了省事,會用下面的方法創(chuàng)建并啟動一個線程,這是匿名內(nèi)部類的寫法,new Thread
需要一個 implements 自Runnable
類型的對象實例作為參數(shù),比較好的方式是創(chuàng)建一個新類,這個類 implements Runnable
,然后 new 出這個新類的實例作為參數(shù)傳給 Thread。而匿名內(nèi)部類不用找對象接收,直接當做參數(shù)。
new?Thread(new?Runnable()?{ ????@Override ????public?void?run()?{ ????????System.out.println("快速新建并啟動一個線程"); ????} }).run();
但是這樣寫是不是感覺看上去很亂、很土,而這時候,換上 Lambda 表達式就是另外一種感覺了。
new?Thread(()->{ ????System.out.println("快速新建并啟動一個線程"); }).run();
怎么樣,這樣一改,瞬間感覺清新脫俗了不少,簡潔優(yōu)雅了不少。
Lambda 表達式簡化了匿名內(nèi)部類的形式,可以達到同樣的效果,但是 Lambda 要優(yōu)雅的多。雖然最終達到的目的是一樣的,但其實內(nèi)部的實現(xiàn)原理卻不相同。
匿名內(nèi)部類在編譯之后會創(chuàng)建一個新的匿名內(nèi)部類出來,而 Lambda 是調(diào)用 JVM invokedynamic
指令實現(xiàn)的,并不會產(chǎn)生新類。
方法引用
方法引用的出現(xiàn),使得我們可以將一個方法賦給一個變量或者作為參數(shù)傳遞給另外一個方法。::
雙冒號作為方法引用的符號,比如下面這兩行語句,引用 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);
相信有的同學看到這里恐怕是下面這個狀態(tài),完全不可理喻嗎,也太隨便了吧,返回給誰都能接盤。
先別激動,來來來,現(xiàn)在咱們就來解惑,解除蒙圈臉。
Q:什么樣的方法可以被引用?
A:這么說吧,任何你有辦法訪問到的方法都可以被引用。
Q:返回值到底是什么類型?
A:這就問到點兒上了,上面又是 Function
、又是Comparator
、又是 IntBinaryOperator
的,看上去好像沒有規(guī)律,其實不然。
返回的類型是 Java 8 專門定義的函數(shù)式接口,這類接口用 @FunctionalInterface
注解。
比如 Function
這個函數(shù)式接口的定義如下:
@FunctionalInterface public?interface?Function<T,?R>?{ ????R?apply(T?t); }
還有很關(guān)鍵的一點,你的引用方法的參數(shù)個數(shù)、類型,返回值類型要和函數(shù)式接口中的方法聲明一一對應(yīng)才行。
比如 Integer.parseInt
方法定義如下:
public?static?int?parseInt(String?s)?throws?NumberFormatException?{ ????return?parseInt(s,10); }
首先parseInt
方法的參數(shù)個數(shù)是 1 個,而 Function
中的 apply
方法參數(shù)個數(shù)也是 1 個,參數(shù)個數(shù)對應(yīng)上了,再來,apply
方法的參數(shù)類型和返回類型是泛型類型,所以肯定能和 parseInt
方法對應(yīng)上。
這樣一來,就可以正確的接收Integer::parseInt
的方法引用,并可以調(diào)用Funciton
的apply
方法,這時候,調(diào)用到的其實就是對應(yīng)的 Integer.parseInt
方法了。
用這套標準套到 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
,兩個參數(shù),并且參數(shù)類型都是 int
。
然后來看Comparator
和IntBinaryOperator
它們兩個的函數(shù)式接口定義和其中對應(yīng)的方法:
@FunctionalInterface public?interface?Comparator<T>?{ ????int?compare(T?o1,?T?o2); } @FunctionalInterface public?interface?IntBinaryOperator?{ ????int?applyAsInt(int?left,?int?right); }
對不對,都能正確的匹配上,所以前面示例中用這兩個函數(shù)式接口都能正常接收。其實不止這兩個,只要是在某個函數(shù)式接口中聲明了這樣的方法:兩個參數(shù),參數(shù)類型是 int
或者泛型,并且返回值是 int
或者泛型的,都可以完美接收。
JDK 中定義了很多函數(shù)式接口,主要在 java.util.function
包下,還有 java.util.Comparator
專門用作定制比較器。另外,前面說的 Runnable
也是一個函數(shù)式接口。
自己動手實現(xiàn)一個例子
1. 定義一個函數(shù)式接口,并添加一個方法
定義了名稱為 KiteFunction 的函數(shù)式接口,使用 @FunctionalInterface
注解,然后聲明了具有兩個參數(shù)的方法 run
,都是泛型類型,返回結(jié)果也是泛型。
還有一點很重要,函數(shù)式接口中只能聲明一個可被實現(xiàn)的方法,你不能聲明了一個 run
方法,又聲明一個 start
方法,到時候編譯器就不知道用哪個接收了。而用default
關(guān)鍵字修飾的方法則沒有影響。
@FunctionalInterface public?interface?KiteFunction<T,?R,?S>?{ ????/** ?????*?定義一個雙參數(shù)的方法 ?????*?@param?t ?????*?@param?s ?????*?@return ?????*/ ????R?run(T?t,S?s); }
2. 定義一個與 KiteFunction 中 run 方法對應(yīng)的方法
在 FunctionTest 類中定義了方法 DateFormat
,一個將 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");
而其實我可以不專門在外面定義 DateFormat
這個方法,而是像下面這樣,使用匿名內(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); }
前面第一個 Runnable
的例子也提到了,這樣的匿名內(nèi)部類可以用 Lambda 表達式的形式簡寫,簡寫后的代碼如下:
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 表達式直接返回方法引用。
Stream API
為了說一下 Stream API 的使用,可以說是大費周章啊,知其然,也要知其所以然嗎,追求技術(shù)的態(tài)度和姿勢要正確。
當然 Stream 也不只是 Lambda 表達式就厲害了,真正厲害的還是它的功能,Stream 是 Java 8 中集合數(shù)據(jù)處理的利器,很多本來復雜、需要寫很多代碼的方法,比如過濾、分組等操作,往往使用 Stream 就可以在一行代碼搞定,當然也因為 Stream 都是鏈式操作,一行代碼可能會調(diào)用好幾個方法。
Collection
接口提供了 stream()
方法,讓我們可以在一個集合方便的使用 Stream API 來進行各種操作。值得注意的是,我們執(zhí)行的任何操作都不會對源集合造成影響,你可以同時在一個集合上提取出多個 stream 進行操作。
我們看 Stream 接口的定義,繼承自 BaseStream
,幾乎所有的接口聲明都是接收方法引用類型的參數(shù),比如 filter
方法,接收了一個 Predicate
類型的參數(shù),它就是一個函數(shù)式接口,常用來作為條件比較、篩選、過濾用,JPA
中也使用了這個函數(shù)式接口用來做查詢條件拼接。
public?interface?Stream<T>?extends?BaseStream<T,?Stream<T>>?{ ??Stream<T>?filter(Predicate<??super?T>?predicate); ??//?其他接口 }??
下面就來看看 Stream 常用 API。
of
可接收一個泛型對象或可變成泛型集合,構(gòu)造一個 Stream 對象。
private?static?void?createStream(){ ????Stream<String>?stringStream?=?Stream.of("a","b","c"); }
empty
創(chuàng)建一個空的 Stream 對象。
concat
連接兩個 Stream ,不改變其中任何一個 Steam 對象,返回一個新的 Stream 對象。
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ù)字類型的屬性比較,擁有最大值的那個實體。它接收一個 Comparator<T>
,上面也舉到這個例子了,它是一個函數(shù)式接口類型,專門用作定義兩個對象之間的比較,例如下面這個方法使用了 Integer::compareTo
這個方法引用。
private?static?void?max(){ ????Stream<Integer>?integerStream?=?Stream.of(2,?2,?100,?5); ????Integer?max?=?integerStream.max(Integer::compareTo).get(); ????System.out.println(max); }
當然,我們也可以自己定制一個 Comparator
,順便復習一下 Lambda 表達式形式的方法引用。
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 用法一樣,只不過是求最小值。
findFirst
獲取 Stream 中的第一個元素。
findAny
獲取 Stream 中的某個元素,如果是串行情況下,一般都會返回第一個元素,并行情況下就不一定了。
count
返回元素個數(shù)。
Stream<String>?a?=?Stream.of("a",?"b",?"c"); long?x?=?a.count();
peek
建立一個通道,在這個通道中對 Stream 的每個元素執(zhí)行對應(yīng)的操作,對應(yīng) Consumer<T>
的函數(shù)式接口,這是一個消費者函數(shù)式接口,顧名思義,它是用來消費 Stream 元素的,比如下面這個方法,把每個元素轉(zhuǎn)換成對應(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 方法類似,都接收一個消費者函數(shù)式接口,可以對每個元素進行對應(yīng)的操作,但是和 peek 不同的是,forEach
執(zhí)行之后,這個 Stream 就真的被消費掉了,之后這個 Stream 流就沒有了,不可以再對它進行后續(xù)操作了,而 peek
操作完之后,還是一個可操作的 Stream 對象。
正好借著這個說一下,我們在使用 Stream API 的時候,都是一串鏈式操作,這是因為很多方法,比如接下來要說到的 filter
方法等,返回值還是這個 Stream 類型的,也就是被當前方法處理過的 Stream 對象,所以 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
是有順序保證的,也就是對 Stream 中元素按插入時的順序進行消費。為什么這么說呢,當開啟并行的時候,forEach
和 forEachOrdered
的效果就不一樣了。
Stream<String>?a?=?Stream.of("a",?"b",?"c"); a.parallel().forEach(e->System.out.println(e.toUpperCase()));
當使用上面的代碼時,輸出的結(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,只不過只能接收一個參數(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
跳過前 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,將重復的 b 只保留了一個。
private?static?void?distinct()?{ ????Stream<String>?a?=?Stream.of("a",?"b",?"c","b"); ????a.distinct().forEach(e->System.out.println(e)); }
sorted
有兩個重載,一個無參數(shù),另外一個有個 Comparator
類型的參數(shù)。
無參類型的按照自然順序進行排序,只適合比較單純的元素,比如數(shù)字、字母等。
private?static?void?sorted()?{ ????Stream<String>?a?=?Stream.of("a",?"c",?"b"); ????a.sorted().forEach(e->System.out.println(e)); }
有參數(shù)的需要自定義排序規(guī)則,例如下面這個方法,按照第二個字母的大小順序排序,最后輸出的結(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)); }
為了更好的說明接下來的幾個 API ,我模擬了幾條項目中經(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("古時的風箏?%s?號",?i)); ????????user.setAge(random.nextInt(100)); ????????user.setGender(i?%?2); ????????user.setPhone("18812021111"); ????????user.setAddress("無"); ????????users.add(user); ????} ????return?users; }
filter
用于條件篩選過濾,篩選出符合條件的數(shù)據(jù)。例如下面這個方法,篩選出性別為 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
方法的接口方法聲明如下,接受一個 Function
函數(shù)式接口,把它翻譯成映射最合適了,通過原始數(shù)據(jù)元素,映射出新的類型。
<R>?Stream<R>?map(Function<??super?T,???extends?R>?mapper);
而 Function
的聲明是這樣的,觀察 apply
方法,接受一個 T 型參數(shù),返回一個 R 型參數(shù)。用于將一個類型轉(zhuǎn)換成另外一個類型正合適,這也是 map
的初衷所在,用于改變當前元素的類型,例如將 Integer
轉(zhuǎn)為 String
類型,將 DAO 實體類型,轉(zhuǎn)換為 DTO 實例類型。
當然了,T 和 R 的類型也可以一樣,這樣的話,就和 peek
方法沒什么不同了。
@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); }
例如下面這個方法,應(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ǔ)上進行封裝。
mapToLong
將元素轉(zhuǎn)換成 Long 類型,在 map
方法的基礎(chǔ)上進行封裝。
mapToDouble
將元素轉(zhuǎn)換成 Double 類型,在 map
方法的基礎(chǔ)上進行封裝。
flatMap
這是用在一些比較特別的場景下,當你的 Stream 是以下這幾種結(jié)構(gòu)的時候,需要用到 flatMap
方法,用于將原有二維結(jié)構(gòu)扁平化。
Stream<String[]>
Stream<Set<String>>
Stream<List<String>>
以上這三類結(jié)構(gòu),通過 flatMap
方法,可以將結(jié)果轉(zhuǎn)化為 Stream<String>
這種形式,方便之后的其他操作。
比如下面這個方法,將List<List<User>>
扁平處理,然后再使用 map
或其他方法進行操作。
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ǔ)上進行封裝。
flatMapToLong
用法參考 flatMap
,將元素扁平為 Long 類型,在 flatMap
方法的基礎(chǔ)上進行封裝。
flatMapToDouble
用法參考 flatMap
,將元素扁平為 Double 類型,在 flatMap
方法的基礎(chǔ)上進行封裝。
collection
在進行了一系列操作之后,我們最終的結(jié)果大多數(shù)時候并不是為了獲取 Stream 類型的數(shù)據(jù),而是要把結(jié)果變?yōu)?List、Map 這樣的常用數(shù)據(jù)結(jié)構(gòu),而 collection
就是為了實現(xiàn)這個目的。
就拿 map 方法的那個例子說明,將對象類型進行轉(zhuǎn)換后,最終我們需要的結(jié)果集是一個 List<UserDto >
類型的,使用 collect
方法將 Stream 轉(zhuǎn)換為我們需要的類型。
下面是 collect
接口方法的定義:
<R,?A>?R?collect(Collector<??super?T,?A,?R>?collector);
下面這個例子演示了將一個簡單的 Integer Stream 過濾出大于 7 的值,然后轉(zhuǎn)換成 List<Integer>
集合,用的是 Collectors.toList()
這個收集器。
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()); }
很多同學表示看不太懂這個 Collector
是怎么一個意思,來,我們看下面這段代碼,這是 collect
的另一個重載方法,你可以理解為它的參數(shù)是按順序執(zhí)行的,這樣就清楚了,這就是個 ArrayList 從創(chuàng)建到調(diào)用 addAll
方法的一個過程。
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); }
我們在自定義 Collector
的時候其實也是這個邏輯,不過我們根本不用自定義, Collectors
已經(jīng)為我們提供了很多拿來即用的收集器。比如我們經(jīng)常用到Collectors.toList()
、Collectors.toSet()
、Collectors.toMap()
。另外還有比如Collectors.groupingBy()
用來分組,比如下面這個例子,按照 userId 字段分組,返回以 userId 為key,List為value 的 Map,或者返回每個 key 的個數(shù)。
//?返回?userId:List<User> Map<String,List<User>>?map?=?user.stream().collect(Collectors.groupingBy(User::getUserId)); //?返回?userId:每組個數(shù) Map<String,Long>?map?=?user.stream().collect(Collectors.groupingBy(User::getUserId,Collectors.counting()));
toArray
collection
是返回列表、map 等,toArray
是返回數(shù)組,有兩個重載,一個空參數(shù),返回的是 Object[]
。
另一個接收一個 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 一個 User 數(shù)組,長度為最后的 Stream 長度。
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
它的作用是每次計算的時候都用到上一次的計算結(jié)果,比如求和操作,前兩個數(shù)的和加上第三個數(shù)的和,再加上第四個數(shù),一直加到最后一個數(shù)位置,最后返回結(jié)果,就是 reduce
的工作過程。
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ì)上來說就是用來做數(shù)據(jù)處理的,為了加快處理速度,Stream API 提供了并行處理 Stream 的方式。通過 users.parallelStream()
或者users.stream().parallel()
的方式來創(chuàng)建并行 Stream 對象,支持的 API 和普通 Stream 幾乎是一致的。
并行 Stream 默認使用 ForkJoinPool
線程池,當然也支持自定義,不過一般情況下沒有必要。ForkJoin 框架的分治策略與并行流處理正好契合。
雖然并行這個詞聽上去很厲害,但并不是所有情況使用并行流都是正確的,很多時候完全沒這個必要。
什么情況下使用或不應(yīng)使用并行流操作呢?
- 必須在多核 CPU 下才使用并行 Stream,聽上去好像是廢話。
- 在數(shù)據(jù)量不大的情況下使用普通串行 Stream 就可以了,使用并行 Stream 對性能影響不大。
- CPU 密集型計算適合使用并行 Stream,而 IO 密集型使用并行 Stream 反而會更慢。
- 雖然計算是并行的可能很快,但最后大多數(shù)時候還是要使用
collect
合并的,如果合并代價很大,也不適合用并行 Stream。 - 有些操作,比如 limit、 findFirst、forEachOrdered 等依賴于元素順序的操作,都不適合用并行 Stream。
以上就是一文帶你徹底了解Java8中的Lambda,函數(shù)式接口和Stream的詳細內(nèi)容,更多關(guān)于Java8 Lambda 函數(shù)式接口 Stream的資料請關(guān)注腳本之家其它相關(guān)文章!
- Java的Lambda表達式和Stream流的作用以及示例
- Java分析Lambda表達式Stream流合并分組內(nèi)對象數(shù)據(jù)合并
- Java中的lambda和stream實現(xiàn)排序
- Java詳細分析Lambda表達式與Stream流的使用方法
- 吊打Java面試官之Lambda表達式 Stream API
- Java8中Lambda表達式使用和Stream API詳解
- 詳解Java遞歸實現(xiàn)樹形結(jié)構(gòu)的兩種方式
- Java實現(xiàn)樹形結(jié)構(gòu)的示例代碼
- Java樹形結(jié)構(gòu)數(shù)據(jù)生成導出excel文件方法記錄
- Java使用 Stream 流和 Lambda 組裝復雜父子樹形結(jié)構(gòu)
相關(guān)文章
Spring實現(xiàn)類私有方法的幾個問題(親測通用解決方案)
現(xiàn)實的業(yè)務(wù)場景中,可能需要對Spring的實現(xiàn)類的私有方法進行測試。本文給大家分享Spring實現(xiàn)類私有方法面臨的幾個問題及解決方案,感興趣的朋友跟隨小編一起看看吧2021-06-06ApiOperation和ApiParam注解依賴的安裝和使用以及注意事項說明
這篇文章主要介紹了ApiOperation和ApiParam注解依賴的安裝和使用以及注意事項說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09如何通過idea實現(xiàn)springboot集成mybatis
這篇文章主要介紹了如何通過idea實現(xiàn)springboot集成mybatis,使用springboot 集成 mybatis后,通過http請求接口,使得通過http請求可以直接操作數(shù)據(jù)庫,本文結(jié)合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-09-09Java如何優(yōu)雅關(guān)閉異步中的ExecutorService
在并發(fā)編程領(lǐng)域,Java的ExecutorService是線程池管理的關(guān)鍵接口,這篇文章主要為大家介紹了如何優(yōu)雅關(guān)閉異步中的ExecutorService,需要的可以了解下2025-02-02