一招刹、Java8
的三個(gè)編程概念
-
流處理
- 從輸入流中一個(gè)一個(gè)讀取數(shù)據(jù)項(xiàng),然后以同樣的方式將數(shù)據(jù)項(xiàng)寫(xiě)入輸出流辩涝。
-
用行為參數(shù)化把代碼傳遞給方法
- 即函數(shù)作為第一公民衰腌,可以作為值來(lái)傳遞
- 并行與共享可變數(shù)據(jù)
二新蟆、流簡(jiǎn)介
?????? Stream API
和 Collection API
的行為差不多,但Collection API
主要為了訪(fǎng)問(wèn)和存儲(chǔ)數(shù)據(jù)右蕊,而Stream API
主要用于描述對(duì)數(shù)據(jù)的計(jì)算琼稻。
?????? 經(jīng)典的Java
程序只能利用單核進(jìn)行計(jì)算,流提供了多核處理數(shù)據(jù)的能力饶囚。但前提是傳遞給Stream API
的方法不會(huì)互動(dòng)(即有可變的共享對(duì)象)時(shí)帕翻,才能多核工作。
三萝风、Lambda
Lambda表達(dá)式由 參數(shù)列表 嘀掸、箭頭 和 主體 組成:
四、函數(shù)式接口
函數(shù)式接口指只定義一個(gè)抽象方法的接口
?????? 注:哪怕有再多默認(rèn)方法规惰,只要接口中只定義了一個(gè)抽象方法睬塌,它仍然是函數(shù)式接口。
?????? Lambda允許你直接以?xún)?nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實(shí)現(xiàn)歇万,并把其作為函數(shù)式接口的實(shí)例揩晴。
FunctionalInterface
注解
?????? @FunctionalInterface
用于表示該接口為函數(shù)式接口。如果它不是函數(shù)式接口的話(huà)贪磺,編譯器將返回一個(gè)提示原因的錯(cuò)誤硫兰。
?????? 注:@FunctionalInterface
不是必需的,但最好為函數(shù)式接口都標(biāo)注@FunctionalInterface
.
函數(shù)描述符
?????? 函數(shù)式接口的抽象方法的基本簽名 本質(zhì)上就是 Lambda
表達(dá)式的簽名寒锚。Java8
將這種抽象方法叫作函數(shù)描述符劫映。
?????? Runnable
接口的run
方法即不接受任何參數(shù)也不返回,其函數(shù)描述符為:() -> void
刹前。 該函數(shù)描述符代表了函數(shù)列表為空且返回void的函數(shù)苏研。
?????? Scala
、Kotlin
等語(yǔ)言在其類(lèi)型系統(tǒng)中提供 顯式的類(lèi)型注釋 來(lái)描述函數(shù)的類(lèi)型(即函數(shù)類(lèi)型)
函數(shù)接口 | 函數(shù)描述符 | 基本類(lèi)型特化 |
---|---|---|
Predicate<T> |
T -> boolean |
IntPredicate LongPredicate , DoublePredicate
|
Consumer<T> |
T -> void |
IntConsumer , LongConsumer , DoubleConsumer
|
Function<T,R> |
T -> R |
IntFunction , IntToDoubleFunction , IntToLongFunction , LongFunction , LongToDoubleFunction , LongToIntFunction , DoubleFunction , ToIntFunction , ToDoubleFunction , ToLongFunction
|
Supplier<T> |
() -> T |
BooleanSupplier , IntSupplier , LongSupplier , DoubleSupplier
|
五腮郊、方法引用
方法引用可以把現(xiàn)有方法像
Lambda
一樣傳遞摹蘑。
方法引用主要分三類(lèi):
- 指向靜態(tài)方法的方法引用。(例如
Integer
的parseInt
方法轧飞,寫(xiě)作Integer::parseInt
) - 指向任意類(lèi)型實(shí)例方法的方法引用.(例如
String
的length
,寫(xiě)作String::length)- 適用于對(duì)象作為
Lambda
表達(dá)式的一個(gè)參數(shù)衅鹿。
- 適用于對(duì)象作為
- 指向現(xiàn)存對(duì)象或表達(dá)式實(shí)例方法的方法引用
- 適用于調(diào)用現(xiàn)存外部對(duì)象的方法。
- 適用于內(nèi)部的私有方法过咬。
注:構(gòu)造函數(shù)大渤、數(shù)組構(gòu)造函數(shù)以及父類(lèi)調(diào)用的方法引用形式比較特殊:
利用 類(lèi)名 和 關(guān)鍵字 new 來(lái)生成構(gòu)造方法的方法引用。
-
對(duì)于默認(rèn)構(gòu)造函數(shù)掸绞,可以使用
Supplier
簽名泵三。Supplier<Apple> c1 = Apple::new; //等價(jià)于: Supplier<Apple> c1 = () -> new Apple();
-
對(duì)于存在參數(shù)的構(gòu)造方法耕捞,可根據(jù)參數(shù)情況尋找適合的函數(shù)式接口的簽名。
Function<Integer,Apple> c2 = Apple::new; //等價(jià)于: Function<Integer,Apple> c2 = (weight) -> new Apple(weight);
六烫幕、流
從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列 —— 流
流允許以聲明性方式處理數(shù)據(jù)集合俺抽。還可以透明地并行處理,無(wú)須寫(xiě)任何多線(xiàn)程代碼较曼。
注:
- 流只遍歷一次磷斧。遍歷完后,流被消費(fèi)了捷犹,需要重新從原始數(shù)據(jù)源那里再次獲取一個(gè)新的流進(jìn)行遍歷弛饭。
- 只有觸發(fā)終端操作,中間操作才會(huì)被執(zhí)行萍歉。
- 中間操作一般都可以合并起來(lái)侣颂,在終端操作中一次性全部處理。
篩選
-
filter
方法:接受一個(gè)謂詞(一個(gè)返回boolean
的函數(shù))作為參數(shù)枪孩,并返回一個(gè)包括所有符合謂詞的元素的流憔晒。//輸出結(jié)果:[1, 3, 0] List<Integer> numbers = Arrays.asList(1,3,8,6,0,7,5,6); numbers.stream() //篩選只小于4的元素 .filter(i -> i < 4) .collect(Collectors.toList());
-
distinct
方法:依據(jù)流所生成元素的hashCode
和equals
方法,返回一個(gè)元素各異的流销凑。(即返回一個(gè)沒(méi)有重復(fù)元素的流)//輸出結(jié)果為:[2,4] List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4); numbers.stream() .filter(i -> i % 2 == 0) //一共存在3個(gè)元素符合filter篩選丛晌,而這其中存在重復(fù)的2仅炊。distinct()只會(huì)返回2和4 .distinct() .collect(Collectors.toList());
流的切片
-
takeWhile
方法:在第一個(gè) 不符合 要求的元素時(shí)停止處理斗幼。
//輸出結(jié)果為:[1, 2, 3, 3]
//在初始列表中的數(shù)據(jù)已排序的情況下:
List<Integer> numbers = Arrays.asList(1,2,3,3,4,4,5,6);
numbers.stream()
//當(dāng)發(fā)現(xiàn)第一個(gè) i < 4 為 false 的元素時(shí),則停止處理
.takeWhile(i -> i < 4)
.collect(Collectors.toList());
-
dropWhile
方法:在第一個(gè) 符合 要求的元素時(shí)停止處理抚垄,并返回所有剩余的元素蜕窿。
//輸出結(jié)果:[4, 4, 5, 6]
//在初始列表中的數(shù)據(jù)已排序(由高到低)的情況下:
List<Integer> numbers = Arrays.asList(1,2,3,3,4,4,5,6);
numbers.stream()
//當(dāng)發(fā)現(xiàn)第一個(gè)i < 4 為 true 的元素時(shí),則停止處理,并返回所有剩余的元素呆馁。
.dropWhile(i -> i < 4)
.collect(Collectors.toList());
-
limit
方法:返回一個(gè)不超過(guò)給定長(zhǎng)度的流桐经。- 如果流是有序的(如:源是
List
),則按順序返回前n
個(gè)元素浙滤。 - 如果流是無(wú)序的(如:源是
set
)阴挣,則不會(huì)以任意順序排序。 - 對(duì)于無(wú)限流纺腊,可以使用
limit
將其變成有限流畔咧。
- 如果流是有序的(如:源是
//輸出結(jié)果:[1, 3]
List<Integer> numbers = Arrays.asList(1,3,8,6,0,7,5,6);
numbers.stream()
//篩選只小于4的元素
.filter(i -> i < 4)
//只返回前兩個(gè)值
.limit(2)
.collect(Collectors.toList());
-
shkip
方法:返回一個(gè)扔掉前n
個(gè)元素的流。- 如果流中元素不足
n
個(gè)揖膜,則返回一個(gè)空流誓沸。
- 如果流中元素不足
//輸出結(jié)果:[3, 0]
List<Integer> numbers = Arrays.asList(1,3,8,6,0,7,5,6);
numbers.stream()
//篩選只小于4的元素
.filter(i -> i < 4)
//跳過(guò)第一個(gè)值
.skip(2)
.collect(Collectors.toList());
映射
-
map
方法:將流中的每一個(gè)元素映射成一個(gè)新的元素。
//輸出結(jié)果:[6, 2, 4, 1]
List<String> languages = Arrays.asList("Kotlin","Go","Java","C");
languages.stream()
//將 字符串 轉(zhuǎn)為 int
.map(String::length)
.collect(Collectors.toList());
-
flatMap
方法:把 一個(gè)流 中的 每一個(gè)值 轉(zhuǎn)換成 另一個(gè)流壹粟,然后把 所有流 連接起來(lái)成一個(gè)流拜隧。- 簡(jiǎn)單說(shuō)就是:把流中的 元素(如:列表,數(shù)組)化為新的流,或把流中的 元素 結(jié)合 **外部的列表 (數(shù)組) ** 化為新的流洪添,再把新的流的元素整合到一個(gè)流中垦页。
//輸出結(jié)果:[K, o, t, l, i, n, G, J, a, v, C]
List<String> languages = Arrays.asList("Kotlin","Go","Java","C");
languages.stream()
.map(str -> str.split(""))
//Arrays::stream 將 str.split("") 返回的字符數(shù)組轉(zhuǎn)換為流,再由 flatMap 統(tǒng)一將這些流合并成一個(gè)流.最終:Stream<String[]> 轉(zhuǎn)換為 Stream<String>,
//flatMap 本質(zhì)也是對(duì)流的元素進(jìn)行轉(zhuǎn)換(map也是對(duì)流的元素進(jìn)行轉(zhuǎn)換)薇组。將流的元素轉(zhuǎn)換為新的流外臂,再將其整合進(jìn)一個(gè)流中。
.flatMap(Arrays::stream) // 等價(jià)于:flatMap(strArray -> Arrays.stream(strArray))
.distinct()
.collect(Collectors.toList());
練習(xí):
1律胀、返回所有對(duì)數(shù)
給定列表[ 1,2,3 ]
和 列表[ 3, 4 ]
,返回[ (1,3) , (1,4) , (2,3) , (2,4) , (3,3) , (3,4) ]
//輸出結(jié)果:[ (1,3) , (1,4) , (2,3) , (2,4) , (3,3) , (3,4) ]
List<Integer> numbers1 = Arrays.asList(1,2,3);
List<Integer> numbers2 = Arrays.asList(3,4);
List<int[]> pairs =
numbers1.stream()
//將其扁平化為一個(gè)流
.flatMap(i ->
numbers2.stream()
//將其轉(zhuǎn)換為一個(gè)數(shù)組宋光,并返回這個(gè)流
.map(j -> new int[]{i,j})
).collect(Collectors.toList());
查找與匹配
-
anyMatch
方法:檢查流中是否至少有一個(gè)元素匹配給定的謂詞。
//輸出結(jié)果:true
List<Integer> numbers = Arrays.asList(1,2,3,5,6,8);
numbers.stream().anyMatch(i -> i > 3);
allMatch
檢查謂詞是否匹配所有元素炭菌。allMatch
方法:檢查流中全部元素都匹配給定的謂詞罪佳。
//輸出結(jié)果:true
List<Integer> numbers = Arrays.asList(1,2,3,5,6,8);
numbers.stream().allMatch(i -> i < 10);
-
noneMatch
方法:檢查流中全部元素都不匹配給定的謂詞。( 與allMatch
相對(duì) )
//輸出結(jié)果:true
List<Integer> numbers = Arrays.asList(1,2,3,5,6,8);
numbers.stream().noneMatch(i -> i > 10);
-
findAny
方法:返回當(dāng)前流中的任意元素黑低。
List<Apple> inventory = Arrays.asList(
new Apple(80,"green"),
new Apple(155, "green"),
new Apple(120, "red"));
Optional<Apple> apple = inventory.stream()
.filter(a -> a.getColor().equals("green"))
.findAny();
-
findFirst
方法:返回當(dāng)前流中的第一個(gè)元素赘艳。
List<Apple> inventory = Arrays.asList(
new Apple(80,"green"),
new Apple(155, "green"),
new Apple(120, "red"));
Optional<Apple> apple = inventory.stream()
.filter(a -> a.getColor().equals("green"))
.findFirst();
注:
??????1、anyMatch
克握、allMatch
和 noneMatch
都屬于終端操作蕾管。
?????? 2、anyMatch
菩暗、allMatch
掰曾、 noneMatch
、 findFirst
和 findAny
不用處理整停团,只要找到一個(gè)元素旷坦,就可以得到結(jié)果了。
?????? 3佑稠、findAny
和 findFirst
同時(shí)存在的原因是 并行 秒梅。findAny
在并行流中限制較少。
歸約
將流中所有元素反復(fù)結(jié)合起來(lái)舌胶,從而得到一個(gè)值的查詢(xún)捆蜀,可以被歸類(lèi)為歸約操作。(用函數(shù)式編程語(yǔ)言的術(shù)語(yǔ)來(lái)說(shuō)幔嫂,這稱(chēng)為折疊)
reduce方法:接收的Lambda
將列表中的所有元素進(jìn)行處理并歸約成一個(gè)新值辆它。
有初始值:
接收一個(gè)初始值 和 一個(gè)BinaryOperator<T>
將兩個(gè)元素結(jié)合起來(lái)產(chǎn)生一個(gè)新值。
T reduce(T identity, BinaryOperator<T> accumulator);
無(wú)初始值:
一個(gè)BinaryOperator<T>
將兩個(gè)元素結(jié)合起來(lái)產(chǎn)生一個(gè)新值婉烟。
Optional<T> reduce(BinaryOperator<T> accumulator);
- 求和
//輸出值:36
List<Integer> numbers = Arrays.asList(1,3,8,6,0,7,5,6);
//使用帶初始值的reduce方法
int sum = numbers.stream()
.reduce(0,Integer::sum);//等價(jià)于 reduce(0,(a,b) -> a + b)
//或使用無(wú)初始值的reduce方法
Optional<Integer> sumOptional = numbers.stream().reduce(Integer::sum);
- 最大值
Optional<Integer> maxOptional = numbers.stream().reduce(Integer::max);
- 最小值
Optional<Integer> minOptional = numbers.stream().reduce(Integer::min);
數(shù)值流
?????? 原先的歸約求和代碼中娩井,Integet::sum
暗含裝箱和拆箱的成本。Stream API
提供了原始類(lèi)型流特化似袁,專(zhuān)門(mén)支持處理數(shù)值流的方法洞辣。Java8
引入原始類(lèi)型特化接口解決數(shù)值流拆箱與裝箱的問(wèn)題:IntStream
咐刨、DoubleStream
和 LongStream
,分別將流中的元素特化為 int
扬霜、 long
和 double
定鸟。
- 映射到數(shù)值流
mapToInt
、mapToDouble
和 mapToLong
用于將流轉(zhuǎn)換為特化流:
//輸出值:36
List<Integer> numbers = Arrays.asList(1,3,8,6,0,7,5,6);
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
- 轉(zhuǎn)換回對(duì)象流
當(dāng)需要把原始流轉(zhuǎn)換成一般流時(shí)(如:把 int
裝箱回 Integer
)著瓶,可以使用 boxed
联予。
List<Integer> numbers = Arrays.asList(1,3,8,6,0,7,5,6);
//使用 IntStrean 特化流
IntStream intStream = numbers.stream()
.mapToInt(Integer::intValue);
Stream<Integer> stream = intStream.boxed();
- 默認(rèn)值
OptionalInt
Optional也相應(yīng)的提供原始類(lèi)型特化版本:OptionalInt
、OptionalLong
和 OptionalDouble
材原。
List<Integer> numbers = Arrays.asList(1,3,8,6,0,7,5,6);
//使用 OptionalInt 特化Optional
OptionalInt maxNumber = numbers.stream()
.mapToInt(Integer::intValue)
.max();
數(shù)值范圍
IntStream
和 LongStream
提供產(chǎn)生生成數(shù)值范圍的靜態(tài)方法:range
和rangeClosed
沸久。
range
方法生成半閉區(qū)間(左閉右開(kāi)),rangeClosed
方法生成閉區(qū)間余蟹。
IntStream.range(1,100)
.filter(n -> n % 2 == 0)
.count();
構(gòu)建流
- 由值創(chuàng)建流
靜態(tài)方法 Stream.of
接受任意數(shù)量的參數(shù)卷胯,顯式創(chuàng)建一個(gè)流。
//顯式創(chuàng)建字符串流
Stream<String> strStream =Stream.of("Java","Kotlin","Go");
靜態(tài)方法Stream.empty
創(chuàng)建一個(gè)空流威酒。
Stream<String> strStream =Stream.empty();
- 由數(shù)組創(chuàng)建流
靜態(tài)方法Arrays.stream
將數(shù)組創(chuàng)建為一個(gè)流窑睁。
int[] numbers = {2,3,5,6,7};
int sum = Arrays.stream(numbers).sum();
- 由文件生成流
java.nio.file.Files
中很多靜態(tài)方法會(huì)返回一個(gè)流,以便利用Stream API
處理文件等I/O
操作葵孤。
如:Files.lines
返回一個(gè)由指定文件中的各行構(gòu)成的字符串流:
long uniqueWords = 0;
//流會(huì)自動(dòng)關(guān)閉担钮,不需要額外try-finally操作
try(Stream<String> lines =
Files.lines(Paths.get("data.text"), Charset.defaultCharset())){
//統(tǒng)計(jì)有多少不重復(fù)的單詞。
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
}catch (IOException e){}
- 由函數(shù)生成流:創(chuàng)建無(wú)限流
Stream API
提供了兩個(gè)靜態(tài)方法來(lái)從函數(shù)生成流:Stream.iterate()
和 String.generate()
不同于從集合創(chuàng)建的流尤仍,這兩個(gè)靜態(tài)方法創(chuàng)建的流沒(méi)有固定大小箫津,稱(chēng)為無(wú)限流。
迭代:
iterate
方法接收一個(gè)接受一個(gè)初始值作為流的第一個(gè)元素吓著。再接收一個(gè)Lambda依次應(yīng)用在每一個(gè)產(chǎn)生的新值上鲤嫡。
Stream.iterate(0,n -> n + 2)
.limit(10)
.forEach(System.out::println);
Java 9
對(duì)iterate
方法進(jìn)行增加送挑,接受多一個(gè)謂詞作為判斷迭代調(diào)用何時(shí)終止绑莺。(謂詞作為第二參數(shù)傳入)
IntStream.iterate(0,n -> n < 100,n -> n + 2)
.forEach(System.out::println);
當(dāng)然,也可以使用takeWhile
對(duì)流執(zhí)行短路操作(takeWhile
函數(shù)Java9
開(kāi)始支持):
IntStream.iterate(0,n -> n + 2)
.takeWhile(n -> n < 100)
.forEach(System.out::println);
生成:
generate
接受一個(gè)Supplier<T>
類(lèi)型的Lambda
提供新值惕耕。
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
七纺裁、用流收集數(shù)據(jù)
流支持兩種類(lèi)型的操作:中間操作 和 末端操作。
- 中間操作可以相互鏈接起來(lái)司澎,將一個(gè)流轉(zhuǎn)換為另一個(gè)流欺缘。中間操作不會(huì)消耗流,目的是建立一個(gè)流水線(xiàn)挤安。
- 末端操作會(huì)消耗流谚殊,以產(chǎn)生一個(gè)最終結(jié)果。
歸約和匯總
-
Collectors
工廠(chǎng)類(lèi)提供了很多歸約的靜態(tài)工廠(chǎng)方法蛤铜。-
Collectors.counting()
用于統(tǒng)計(jì)總和嫩絮。
//求總和 long count = menu.stream().collect(Collections.counting());
-
Collectors.maxBy
和Collectors.minBy
用來(lái)計(jì)算流中的最大值和最小值丛肢。
//求最大值 Optional<Dish> mostCalorieDish = menu.stream().collect( Comparator.maxBy( Comparator.comparingInt(Dish::getCalories) ) );
-
-
同時(shí)
Collectors
類(lèi)專(zhuān)門(mén)為匯總提供了一些工廠(chǎng)方法。-
Collectors.summingInt
剿干、Collectors.summingLong
和Collectors.summingDouble
分別用于對(duì)int
蜂怎、long
和double
進(jìn)行求和。
int sumValue = menu.stream().collect(summingInt(Dish::getCalories));
-
Collectors.averagingInt
置尔、Collectors.averagingLong
和Collectors.averagingDouble
分別用于對(duì)int
杠步、long
和double
進(jìn)行求平均值。
double avgValue = menu.stream().stream().collect(averagingInt(Dish::getCalories));
-
Collectors.joining
工廠(chǎng)方法會(huì)對(duì)流中每一個(gè)對(duì)象應(yīng)用toString
方法得到所有字符串連接成一個(gè)字符串榜轿。
String nameStr = menu.stream().map(Dish::getName).collect(joining());
分組
Collections
的 groupingBy()
方法會(huì)把流中的元素分成不同的組幽歼。
操作分組的元素
- 過(guò)濾
如果在 groupingBy()
之前,使用 filter()
對(duì)流進(jìn)行過(guò)濾操作谬盐,可能會(huì)造成鍵的丟失试躏。
例如:
?????? 存在以下Map: { FISH = [ prawns, salmon], OTHER = [french fries, rice ], MEAT = [pork , beef, chicken] }
但如果在使用filter()
后,再 groupingBy()
可能對(duì)某些鍵在結(jié)果映射中完全消失:
?????? { OTHER = [french fries, rice ], MEAT = [pork , beef, chicken] }
為此设褐,Collectors
類(lèi)提供了 filtering()
靜態(tài)工廠(chǎng)方法颠蕴,它接受一個(gè)謂詞對(duì)每一個(gè)分組中的元素執(zhí)行過(guò)濾操作。最后不符合謂詞條件的鍵將得到空的列表:
{ FISH = [], OTHER = [french fries, rice ], MEAT = [pork , beef, chicken] }
Map<Dish.Type,List<Dish>> caloricDishesByType = menu.stream()
.collect( groupingBy(Dish::getType),
filtering(dish -> dish.getCalories() > 500,toList()))
注:
?????? 使用重載的 groupingBy()
方法 和 filtering()
方法 :先分組再過(guò)濾助析;
?????? 先使用 filter()
犀被,再使用 groupingBy()
方法:先過(guò)濾再分組。
- 映射
Collectors
提供 mapping
靜態(tài)工廠(chǎng)方法外冀,接受一個(gè)映射函數(shù)和另外一個(gè) Collectors
函數(shù)作為參數(shù)寡键。映射函數(shù)將分組中的元素進(jìn)行轉(zhuǎn)換,作為參數(shù)的 Collectors
函數(shù)會(huì)收集對(duì)每個(gè)元素執(zhí)行該映射函數(shù)的結(jié)果雪隧。
Map<Dish.Type,List<String>> dishNamesByType = menu.stream().collect(
groupingBy(
Dish::getType,
mapping(
//將元素轉(zhuǎn)換為其名字
Dish::getName,
//用于收集該組進(jìn)行完映射的元素
Collectors.toList()
)
)
)
Collectors
工具類(lèi)也提供了 flatMapping
,跟 flatMap
類(lèi)似的功能西轩。
多級(jí)分組
同時(shí)Collectors
工具類(lèi)也提供了可以嵌套分組的groupingBy()
,用于進(jìn)行多級(jí)分組
注:
?????? 可以理解為在進(jìn)行完第一次分組后,再對(duì)每一組元素進(jìn)行再次分組脑沿。
?????? groupingBy(f)
( f 是分類(lèi)函數(shù) ) 實(shí)際上是groupingBy(f藕畔,toList())
的簡(jiǎn)便寫(xiě)法。
Map<Dish.Type,Map<CaloricLevel,List<Dish>>> dishesByTypeCaloricLevel =
menu.stream().collect(
groupingBy(
Dish::getType,
groupingBy(dish -> {
if(dish.getCalories() <= 400)
return CaloricLevel.DIET;
else if(dish.getCalories() <= 700)
return CaloricLevel.NORMAL;
else
return CaloricLevel.FAT;
})
)
);
按子組收集數(shù)據(jù)
groupingBy()
的第二個(gè)收集器可以是任何類(lèi)型庄拇。例如可以使用 counting()
收集器作為它的第二個(gè)參數(shù)注服,統(tǒng)計(jì)分組的數(shù)量:
Map<Dish.TYPE,Long> typesCount = menu.stream().collect(
groupingBy(Dish::getType,counting())
);
得到以下的map: { MEAT = 3 , FISH = 2 , OTHER = 4 }
Map<Dish.Type,Dish> mostCaloricByType =
menu.stream().collect(
groupingBy(Dish::getTpye,
collectingAndThen(
//maxBy返回的是Optional類(lèi)型對(duì)象
maxBy(comparingInt(Dish::getCalories)),
//當(dāng)找到最大值后,會(huì)執(zhí)行g(shù)et操作措近。
Optional::get
)
)
);
如果 menu
中沒(méi)有某一類(lèi)型的Dish
,該類(lèi)型不會(huì)對(duì)應(yīng)一個(gè) Optional.empty()
值溶弟,而且根本不會(huì)在Map
的鍵中。所以轉(zhuǎn)換函數(shù)Optional::get
的操作是安全的瞭郑。
分區(qū)
Collectors
工具類(lèi)提供 partitionedMenu()
靜態(tài)工廠(chǎng)函數(shù)來(lái)實(shí)現(xiàn)分區(qū)辜御,分區(qū)是分組的特殊情況。由謂詞作為分類(lèi)函數(shù)屈张,這意味著得到的分組 Map
的鍵類(lèi)型是 Boolean
,最多分為 true
和 false
兩組擒权。
//將得到以下結(jié)果:
Map<Boolean,List<Dish>> partitionedMenu =
menu.stream().collect(
//分區(qū)函數(shù)
partitioningBy(
//分期的標(biāo)準(zhǔn)
Dish::isVegetarian
)
)
同時(shí)partitionedMenu()
也和groupingBy()
類(lèi)似苇本,可以進(jìn)行二級(jí)分區(qū)。
收集器接口
public interface Collector<T, A, R> {
//創(chuàng)建一個(gè)空的累加器
Supplier<A> supplier();
//將元素添加到結(jié)果容器
BiConsumer<A, T> accumulator();
//合并兩個(gè)結(jié)果(定義了對(duì)流的各個(gè)子部分進(jìn)行并行處理時(shí)菜拓,各個(gè)子部分歸約所得的累加器如何合并)
BinaryOperator<A> combiner();
//對(duì)結(jié)果容器應(yīng)用最終轉(zhuǎn)換
Function<A, R> finisher();
//定義收集器的行為
Set<Characteristics> characteristics();
}
泛型的定義如下:
?????? T 表示流中要手機(jī)的項(xiàng)目的泛型瓣窄。
?????? A 表示累加器的類(lèi)型。(累加器是收集過(guò)程中用于累積部分結(jié)果的對(duì)象)
?????? R 表示收集操作得到的對(duì)象的類(lèi)型纳鼎。
以ToListCollector
為例
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {
public ToListCollector() {}
//創(chuàng)建ArrayList對(duì)象作為累加器
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
//利用add函數(shù)將流中的元素添加到列表中
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
//兩個(gè)累加器(即兩個(gè)ArrayList對(duì)象)進(jìn)行相加
public BinaryOperator<List<T>> combiner() {
return (list, list2) -> {
list.addAll(list2);
return list;
};
}
//累加器進(jìn)行最終的轉(zhuǎn)換
public Function<List<T>, List<T>> finisher() {
//Function.identity()表示給什么返回什么俺夕,也就是不進(jìn)行轉(zhuǎn)換
//恒等
return Function.identity();
}
//定義收集器的行為
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT));
}
}
Characteristics的三個(gè)枚舉:
- UNORDERED —— 歸約結(jié)果不受流中項(xiàng)目的遍歷和累積順序的影響。
- CONCURRENT—— accumulator 函數(shù)可以從多個(gè)線(xiàn)程同時(shí)調(diào)用贱鄙,且該收集器可以并行歸約流劝贸。(僅僅只是數(shù)據(jù)源無(wú)序時(shí)才會(huì)并行處理)
- IDENTITY_FINISH—— 表明完成器方法返回的函數(shù)是一個(gè)恒等函數(shù),可以跳過(guò)逗宁。累加器對(duì)象會(huì)直接用作歸約過(guò)程的最終結(jié)果映九。這也意味著,將累加器A不加檢查的轉(zhuǎn)換為結(jié)果R是安全的瞎颗。
進(jìn)行自定義收集件甥,而不去實(shí)現(xiàn) Collector
對(duì)于 IDENTITY_FINISH
的收集操作,Stream
重載的 collect
方法接受三個(gè)函數(shù)——supplier
哼拔、accumulator
和 combiner
引有。該 collect
方法創(chuàng)建的收集器的 Characteristics
永遠(yuǎn)是Characteristics.IDENTITY_FINISH
和 Characteristics.CONCURRENT
List<Dish> dishes = menu.stream().collect(
//創(chuàng)建累加容器
ArrayList::new,
//將流元素添加到累加容器中
List::add,
//合并累加容器
List::addAll
);
八、并行數(shù)據(jù)處理與性能
- 對(duì)順序流調(diào)用
parallel()
方法并不意味著流本身有任何實(shí)際的變化倦逐,它僅僅在內(nèi)部設(shè)置了一個(gè)boolean標(biāo)志譬正,表示你想讓調(diào)用parallel()
之后的所有操作都并行執(zhí)行。對(duì)并行流調(diào)用sequential
方法就可以把它變成順序流檬姥。 - 并行流默認(rèn)的線(xiàn)程數(shù)量等于你處理器的核數(shù)曾我。
使用并行流時(shí),考慮以下因素:
- 留意自動(dòng)裝箱和拆箱健民。(應(yīng)盡量將其轉(zhuǎn)為原始類(lèi)型流)
- 對(duì)于較小數(shù)據(jù)量抒巢,無(wú)需使用并行流。
- 考慮流背后的數(shù)據(jù)結(jié)構(gòu)是否容易分解荞雏。
- 部分操作本身在并行流上的性能比順序流差虐秦。如:
limit
和findFirst
- 考慮合并 步驟的代價(jià)是大是小平酿。
- 考慮操作流水線(xiàn)的總操作成本凤优。當(dāng)單個(gè)元素通過(guò)流水線(xiàn)的成本較高時(shí),使用并行流比較好蜈彼。
流的數(shù)據(jù)源和可分解性:
源 | 可分解性 |
---|---|
ArrayList |
差 |
LinkedList |
差 |
IntStream.range |
極佳 |
Stream.iterate |
差 |
HashSet |
好 |
TreeSet |
好 |
九筑辨、Collection API
的增強(qiáng)功能
Arrays.asList()
創(chuàng)建一個(gè)固定大小的列表,列表的元素可以更新幸逆,但不可以增加或刪除棍辕。
Java 9 引入以下工廠(chǎng)方法:
List.of
——?jiǎng)?chuàng)建一個(gè)只讀列表暮现,不可set
、add
等操作楚昭。Set.of
—— 創(chuàng)建一個(gè)只讀的Set
集合栖袋。-
Map.of
—— 接受的列表中,以鍵值交替的方式創(chuàng)建map
的元素抚太。塘幅、- 當(dāng)創(chuàng)建Map的鍵值對(duì)過(guò)多時(shí),可以使用
map.ofEntries()
和Map.entry()
創(chuàng)建map.
import static java.util.Map.entry; Map<String,Integer> ageOfFriends = Map.ofEntries( entry("Raphael",30), entry("Olivia",25), entry("Thibaut",26) );
- 當(dāng)創(chuàng)建Map的鍵值對(duì)過(guò)多時(shí),可以使用
重載與變參
在Java API
中尿贫,List.of
包含多個(gè)重載版本:
static <E> List<E> of(E e1);
static <E> List<E> of(E e1, E e2);
而不提供變參版本是因?yàn)樾枰~外的分配一個(gè)數(shù)組电媳,這個(gè)數(shù)組被封裝于列表中。使用變參版本的方法庆亡,就要負(fù)擔(dān)分配數(shù)組匾乓、初始化以及最后進(jìn)行垃圾回收的開(kāi)銷(xiāo)。(如果元素?cái)?shù)量超過(guò)10個(gè)又谋,實(shí)際調(diào)用的還是變參方法拼缝。)、
使用 List
彰亥、 Set
和 Map
-
removeIf()
—— 移除集合中匹配指定謂詞的元素珍促。(該方法由Collection
接口提供默認(rèn)方法,List
和Set
都可用)//Collection.java //Predicate(謂詞)的函數(shù)描述符是:(T) -> boolean default boolean removeIf(Predicate<? super E> filter)
當(dāng)使用for-each遍歷列表剩愧,進(jìn)行移除操作時(shí)猪叙,會(huì)導(dǎo)致
ConcurrentModificationException
.因?yàn)楸闅v使用的迭代器對(duì)象和集合對(duì)象的狀態(tài)同步。我們只能顯示調(diào)用迭代器對(duì)象(Iterator
對(duì)象)的remove
方法仁卷。因此Java8
提供removeIf
方法穴翩,安全簡(jiǎn)便的刪除符合謂詞的元素。
-
replaceAll()
—— 使用一個(gè)函數(shù)替換List
或Map
中的元素锦积。(該方法由List
接口提供默認(rèn)方法)//List.java //UnaryOperator的函數(shù)描述符是:(T) -> T default void replaceAll(UnaryOperator<E> operator)
該函數(shù)只是在列表內(nèi)部進(jìn)行同類(lèi)型的轉(zhuǎn)換芒帕,并沒(méi)有創(chuàng)建新的列表。也就是說(shuō)初始為
List<String>
丰介,函數(shù)執(zhí)行完還是List<String>
.//Map.java // BiFunction的函數(shù)描述符是:(K,V) -> V default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
-
sort()
—— 對(duì)列表自身進(jìn)行排序背蟆。(該方法由List
接口提供默認(rèn)方法)//List.java //Comparator的函數(shù)描述符是:(T,T) -> boolean default void sort(Comparator<? super E> c)
-
forEach()
—— List 和 Set,甚至是Map在Java8
中都支持forEach
方法哮幢。而遍歷提供的便捷带膀,特別是Map的遍歷。//Iterable.java //Consumer(消費(fèi)者)的函數(shù)描述符是:(T) -> void default void forEach(Consumer<? super T> action) //Map.java //BiConsumer(二元消費(fèi)者)的函數(shù)描述符是:(T,U) -> void default void forEach(BiConsumer<? super K, ? super V> action)
Entry.comparingByValue()
和Entry.comparingByKey()
—— 對(duì)Map的值或鍵進(jìn)行排序橙垢。-
Map.compute
—— 使用指定的鍵計(jì)算新的值垛叨,并將其存儲(chǔ)到Map中,并返回新值柜某。嗽元。(指定一個(gè)key
,再提供一個(gè)BiFunction
敛纲,依據(jù)key和舊值,計(jì)算新值剂癌。如果新值為null
,則不會(huì)加入到Map
中并將舊值移除淤翔。)//BiFunction的函數(shù)描述符是:(K,V) -> V default V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)
-
Map.computeIfAbsent
—— 如果指定的鍵沒(méi)有對(duì)應(yīng)的值(沒(méi)有該鍵或者該鍵對(duì)應(yīng)的值是空),使用該鍵計(jì)算新的值佩谷,并添加到Map
中(如果新值為null
,則不會(huì)加入到Map
中并將舊值移除。)琳要,并返回新值寡具。//Function的函數(shù)描述符是:(K) -> V default V computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction)
-
該方法對(duì)于值需要初始化時(shí)有用。比如向
Map<K,List<V>>
添加一個(gè)元素( 初始化對(duì)應(yīng)的ArrayList
稚补,并返回該值):map.computeIfAbsent("daqi", name -> new ArrayList<>()) .add("Java8")
-
Map.computeIfPresent
—— 如果指定的鍵在Map中存在童叠,依據(jù)該鍵和舊值計(jì)算該鍵的新值,并將其添加到Map中课幕。(如果新值為null
,則不會(huì)加入到Map
中厦坛,并將舊值移除。)//BiFunction的函數(shù)描述符是:(K,V) -> V default V computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)
-
Map.remove
—— 重載版本的remove
可以刪除Map
中某個(gè)鍵對(duì)應(yīng)某個(gè)特定值的映射對(duì)乍惊。(即Key
和Value
都匹對(duì)上杜秸,才從Map
中移除)default boolean remove(Object key, Object value)
-
Map.replace
—— 重載版本的replace
可以?xún)H在原有鍵對(duì)應(yīng)某個(gè)特定的值時(shí)才進(jìn)行替換。(即Key
和Value
都匹對(duì)上润绎,才從Map
中替換)default V replace(K key, V value)
-
Map.merge
—— 如果指定的鍵在Map中存在撬碟,依據(jù)該鍵和舊值計(jì)算該鍵的新值,并將其添加到Map中莉撇; 如果指定的鍵在Map中不存在呢蛤,依據(jù)指定的value作為Key的值,并將其添加到Map中棍郎。//BiFunction的函數(shù)描述符:(V,V) -> V default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction)
-
該函數(shù)可用于
Map
的合并其障,或用于將Collector
轉(zhuǎn)換成Map
.Map<String,Integer> language1 = new HashMap<>(); language1.put("Java",8); language1.put("Kotlin",1); Map<String,Integer> language2 = new HashMap<>(); language2.put("Java",11); language2.put("Go",1); //合并Map language1.forEach((key,value) -> { //Map的value可null,merge函數(shù)不允許value為null if (value != null) language2.merge(key,value,Integer::sum); });
static class Score{ private int score; private int studentId; private String studentName; public Score(int studentId, String studentName,int score) { this.score = score; this.studentId = studentId; this.studentName = studentName; } //get和set方法 } //Collector轉(zhuǎn)換為Map(用途:統(tǒng)計(jì)) List<Score> languageList = new ArrayList<>(); languageList.add(new Score(1,"Java",80)); languageList.add(new Score(2,"Kotlin",90)); languageList.add(new Score(2,"Java",85)); languageList.add(new Score(1,"Kotlin",70)); Map<String,Integer> language3 = new HashMap<>(); //Collectors.toMap(Function<? super T, ? extends K>,Function<? super T, ? extends U>,BinaryOperator<U>)內(nèi)部也是通過(guò)Map.merge()實(shí)現(xiàn)的。 languageList.stream().collect(Collectors.toMap(Score::getStudentId,Score::getScore,Integer::sum));
十涂佃、 重構(gòu)
改善代碼可讀性
-
用
lambda
表達(dá)式取代匿名類(lèi)励翼。匿名類(lèi)和
lambda
表達(dá)式中的this
和super
的含義不同。在匿名類(lèi)中辜荠,this
代表的是類(lèi)自身;在lambda
表達(dá)式中侨拦,this
代表的是包含類(lèi)。-
匿名類(lèi)可屏蔽包含類(lèi)的變量狱从,而lambda表達(dá)式不能(導(dǎo)致編譯報(bào)錯(cuò))。
int a = 10; //lambda表達(dá)式 Runnable r1 = () -> { //idea爆紅敞葛,提示:該變量已在作用域中被定義与涡。 int a = 1; }; //匿名類(lèi) Runnable r2 = new Runnable() { @Override public void run() { //編譯正常 int a = 2; } };
-
匿名內(nèi)部類(lèi)的類(lèi)型是在初始化時(shí)確定的,lambda的類(lèi)型取決于它的上下文氨肌。當(dāng)出現(xiàn)兩個(gè)或以上方法參數(shù)的函數(shù)描述符與
lambda
的函數(shù)描述符匹配時(shí)酌畜,需要顯示的類(lèi)型轉(zhuǎn)換來(lái)解決。interface daqiRunnable{ public void action(); } //無(wú)論Runnable恳守,還是daqiRunnable贩虾,其函數(shù)描述符為() -> void public static void doSomething(Runnable r){} public static void doSomething(daqiRunnable r){} public static void main(String[] args) { //顯示類(lèi)型轉(zhuǎn)換 doSomething((daqiRunnable) () -> {}); }
-
用方法引用 重構(gòu)
lambda
表達(dá)式缎罢,提高代碼的可讀性。將較復(fù)雜的Lambda邏輯封裝在方法中策精,使用方法引用替代該Lambda。
-
盡量使用靜態(tài)輔助方法蔽午。比如:
comparing
和maxBy
list.sort((a1,a2) -> a1.getWeight().compareTo(a2.getWeight())); //替換成: list.sort(Comparator.comparing(Apple::getWeight));
-
很多通用的歸約操作酬蹋,都可以借助
Collectors
的輔助方法 + 方法引用替代。list.stream() .map(Dish::getCalories) .reduce(0,(c1,c2) -> c1 + c2); //替換成Collectors的輔助方法 list.stream() .collect(summingInt(Dish::getCalories));
用
Stream API
重構(gòu)命令式的數(shù)據(jù)處理
參考資料
Java8系列
Java 8 知識(shí)歸納(一)—— 流 與 Lambda