在一起來學(xué)Java8(七)——Stream(上)中我們了解到了Stream對(duì)象的常用方法以及用法。現(xiàn)在一起來深入了解下Stream.collect()方法的使用
collect基本用法
collect意思為收集,它是對(duì)Stream中的元素進(jìn)行收集和歸納,返回一個(gè)新的集合對(duì)象。先來看一個(gè)簡(jiǎn)單例子:
public class CollectTest {
public class CollectTest {
@Data
@AllArgsConstructor
static class Goods {
private String goodsName;
private int price;
}
public static void main(String[] args) {
List<Goods> list = Arrays.asList(
new Goods("iphoneX", 4000)
, new Goods("mate30 pro", 5999)
, new Goods("redmek20", 2999)
);
List<String> nameList = list.stream()
.map(Goods::getGoodsName)
.collect(Collectors.toList());
}
}
在這個(gè)例子中,通過map方法返回商品名稱,然后把所有的商品名稱放到了List對(duì)象中。
查看源碼發(fā)現(xiàn),collect方法由兩個(gè)重載方法組成。
- 方法1:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
- 方法2:
<R, A> R collect(Collector<? super T, A, R> collector);
其中用的最多的是方法2,這個(gè)方法可以看做是方法1的快捷方式,因?yàn)镃ollector中同樣提供了Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner這三個(gè)參數(shù),不難猜測(cè)其底層還是要用到方法1對(duì)應(yīng)的實(shí)現(xiàn)。
我們可以先從collect(Collector<? super T, A, R> collector)開始入手,通過這個(gè)再去慢慢了解方法1的用法。
Collectors
Stream.collect(Collector<? super T, A, R> collector)方法的參數(shù)Collector對(duì)象主要由Collectors類提供。Collectors類里面包含了一系列的靜態(tài)方法,用來返回Collector對(duì)象,常用的方法如下列表所示:
| 方法名稱 | 描述 |
|---|---|
| averagingXX | 求平均數(shù) |
| counting | 求集合中元素個(gè)數(shù) |
| groupingBy | 對(duì)集合進(jìn)行分組 |
| joining | 對(duì)集合元素進(jìn)行拼接 |
| mapping | 可在分組的過程中再次進(jìn)行值的映射 |
| maxBy | 求最大值 |
| minBy | 求最小值 |
| partitioningBy | 對(duì)元素進(jìn)行分區(qū) |
| reducing | 歸納 |
| summarizingXX | 匯總 |
| toCollection | 轉(zhuǎn)換成集合對(duì)象 |
| toConcurrentMap | 轉(zhuǎn)換成ConcurrentMap |
| toList | 轉(zhuǎn)換成List |
| toMap | 轉(zhuǎn)換成Map |
| toSet | 轉(zhuǎn)換成Set |
下面依次來講解下每個(gè)方法的用處。
averagingXX
averagingXX包括averagingDouble,averagingInt,averagingLong。它們表示求平均值。
double averagingInt = Stream.of(1, 2, 3)
.collect(Collectors.averagingInt(val -> val));
System.out.println("averagingInt:" + averagingInt);
double averagingLong = Stream.of(10L, 21L, 30L)
.collect(Collectors.averagingLong(val -> val));
System.out.println("averagingLong:" + averagingLong);
double averagingDouble = Stream.of(0.1, 0.2, 0.3)
.collect(Collectors.averagingDouble(val -> val));
System.out.println("averagingDouble:" + averagingDouble);
它們的參數(shù)是一個(gè)函數(shù)式接口,可以使用Lambda表達(dá)式編寫,其中Lambda表達(dá)式中的參數(shù)為Stream中的元素,返回的是待求平均的數(shù)值。下面這則列子是求商品的平均值:
List<Goods> list = Arrays.asList(
new Goods("iphoneX", 4000)
, new Goods("mate30 pro", 5999)
, new Goods("redmek20", 2999)
);
double avgPrice = list.stream()
.collect(Collectors.averagingInt(goods -> goods.getPrice()));
System.out.println("商品的平均價(jià)格:" + avgPrice);
summingXX
與averagingXX類似,summingXX方法用來求集合中的元素值的總和。
double summingInt = Stream.of(1, 2, 3)
.collect(Collectors.summingInt(val -> val));
System.out.println("summingInt:" + summingInt);
double summingLong = Stream.of(10L, 21L, 30L)
.collect(Collectors.summingLong(val -> val));
System.out.println("summingLong:" + summingLong);
double summingDouble = Stream.of(0.1, 0.2, 0.3)
.collect(Collectors.summingDouble(val -> val));
System.out.println("summingDouble:" + summingDouble);
打?。?/p>
summingInt:6.0
summingLong:61.0
summingDouble:0.6
counting()
counting()返回集合中元素個(gè)數(shù)。
long count = Stream.of(1,2,3,4,5)
.collect(Collectors.counting());
System.out.println("count:" + count); // 5
summarizingXX
上面講到了averagingXX(求平均)、summingXX(求和)、counting(求總數(shù)),如果我要同時(shí)獲取這三個(gè)數(shù)該怎么辦呢,可以用summarizingXX。
IntSummaryStatistics summarizingInt = Stream.of(1, 2, 3)
.collect(Collectors.summarizingInt(val -> val));
System.out.println("平均值:" + summarizingInt.getAverage());
System.out.println("總個(gè)數(shù):" + summarizingInt.getCount());
System.out.println("總和:" + summarizingInt.getSum());
System.out.println("最大值:" + summarizingInt.getMax());
System.out.println("最小值:" + summarizingInt.getMin());
打?。?/p>
平均值:2.0
總個(gè)數(shù):3
總和:6
最大值:3
最小值:1
summarizingInt將統(tǒng)計(jì)結(jié)果放到了一個(gè)IntSummaryStatistics對(duì)象里面,在對(duì)象中可以獲取不同的統(tǒng)計(jì)信息。
groupingBy()
groupingBy()是對(duì)集合中的元素進(jìn)行分組,由三個(gè)重載方法組成
- 重載1: groupingBy(Function)
- 重載2: groupingBy(Function, Collector)
- 重載3: groupingBy(Function, Supplier, Collector)
其中重載1調(diào)用了重載2,重載2調(diào)用重載3,因此最終都會(huì)執(zhí)行到重載3中來。
首先看下重載1groupingBy(Function)的用法,這個(gè)方法默認(rèn)分組到新的List中,下面這個(gè)例子對(duì)商品類型進(jìn)行分組,同樣的類型的商品放到一個(gè)List中。
@Data
@AllArgsConstructor
static class Goods {
private String goodsName;
// 類型,1:手機(jī),2:電腦
private int type;
@Override
public String toString() {
return goodsName;
}
}
public static void main(String[] args) {
List<Goods> list = Arrays.asList(
new Goods("iphoneX", 1)
, new Goods("mate30 pro", 1)
, new Goods("thinkpad T400", 2)
, new Goods("macbook pro", 2)
);
Map<Integer, List<Goods>> goodsListMap = list.stream()
.collect(Collectors.groupingBy(Goods::getType));
goodsListMap.forEach((key, value) -> {
System.out.println("類型" + key + ":" + value);
});
}
打?。?/p>
類型1:[iphoneX, mate30 pro]
類型2:[thinkpad T400, macbook pro]
上面說到了groupingBy(Function)實(shí)際上是調(diào)用了groupingBy(Function, Collector),其中第二個(gè)參數(shù)Collector決定了轉(zhuǎn)換到哪里,默認(rèn)是toList(),參見groupingBy(Function)的源碼:
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
因此我們可以調(diào)用groupingBy(Function, Collector)手動(dòng)指定Collector,假設(shè)我們要把轉(zhuǎn)換后的元素放到Set當(dāng)中,可以這樣寫:
Map<Integer, Set<Goods>> goodsListMap = list.stream()
.collect(Collectors.groupingBy(Goods::getType, Collectors.toSet()));
查看重載2方法源碼,發(fā)現(xiàn)其調(diào)用了重載3:
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
其中Goods::getType對(duì)應(yīng)classifier,Collectors.toSet()對(duì)應(yīng)downstream。中間那個(gè)參數(shù)HashMap::new意思很明顯了,即返回的Map的具體實(shí)現(xiàn)類是哪個(gè),如果要改成LinkedHashMap,可以這樣寫:
LinkedHashMap<Integer, Set<Goods>> goodsListMap = list.stream()
.collect(Collectors.groupingBy(Goods::getType, LinkedHashMap::new, Collectors.toSet()));
這正是重載3的使用方式。
Collectors中的groupingByConcurrent方法正是基于重載3而來,中間的代碼改成了ConcurrentHashMap::new而已。
public static <T, K>
Collector<T, ?, ConcurrentMap<K, List<T>>>
groupingByConcurrent(Function<? super T, ? extends K> classifier) {
return groupingByConcurrent(classifier, ConcurrentHashMap::new, toList());
}
groupingBy方法中的Collector參數(shù)不僅僅只可以toList(),toSet(),它還有更加靈活的用法,之前我們轉(zhuǎn)換的都是Map<Integer, List<Goods>>形式,value中存放的是集合對(duì)象,如果不想要那么多屬性,只想要對(duì)象里面的商品名稱,,也就是說我們想得到Map<Integer, List<String>>,其中key為商品類型,value為商品名稱集合。
這個(gè)時(shí)候Collectors.mapping()就派上用場(chǎng)了,我們使用groupingBy(Function, Collector)方法,第二參數(shù)傳Collectors.mapping()
Map<Integer, List<String>> goodsListMap =
list.stream()
.collect(
Collectors.groupingBy(
Goods::getType,
Collectors.mapping(Goods::getGoodsName, Collectors.toList())
)
);
mapping()方法有兩個(gè)參數(shù),第一參數(shù)指定返回的屬性,第二個(gè)參數(shù)指定返回哪種集合。
joining
joining方法可以把Stream中的元素拼接起來。
List<String> list = Arrays.asList("hello", "world");
String str = list.stream().collect(Collectors.joining());
System.out.println(str); // 打印:helloworld
還可以指定分隔符:
List<String> list = Arrays.asList("hello", "world");
String str = list.stream().collect(Collectors.joining(","));
System.out.println(str); // 打?。篽ello,world
除此之外,String類提供了一個(gè)join方法,功能是一樣的
String str2 = String.join(",", list);
System.out.println(str2);
maxBy&minBy
- maxBy:找出Stream中最大的元素
@Data
@AllArgsConstructor
static class Goods {
private String goodsName;
private int price;
}
public static void main(String[] args) {
List<Goods> list = Arrays.asList(
new Goods("iphoneX", 4000)
, new Goods("mate30 pro", 5999)
, new Goods("redmek20", 2999)
);
Goods maxPriceGoods = list.stream()
.collect(
Collectors.maxBy(
Comparator.comparing(Goods::getPrice)
)
)
.orElse(null);
System.out.println("最貴的商品:" + maxPriceGoods);
}
上面的例子演示了查找最貴的商品,Collectors.maxBy()方法需要傳入一個(gè)比較器,需要根據(jù)商品的價(jià)格來比較。
同理,找到最便宜的商品只需把maxBy替換成minBy即可。
partitioningBy
partitioningBy方法表示分區(qū),它將根據(jù)條件將Stream中的元素分成兩部分,并分別放入到一個(gè)Map當(dāng)中,Map的key為Boolean類型,key為true部分存放滿足條件的元素,key為false存放不滿足條件的元素。
{
true -> 符合條件的元素
false -> 不符合條件的元素
}
partitioningBy方法由兩個(gè)重載方法組成
- 重載1:partitioningBy(Predicate)
- 重載2:partitioningBy(Predicate, Collector)
其中重載1會(huì)調(diào)用重載2,因此最終還是調(diào)用了重載2方法,我們先看下重載1方法。
下面這個(gè)例子根據(jù)商品類型,將商品劃分為手機(jī)類商品和非手機(jī)類商品。
@Data
@AllArgsConstructor
static class Goods {
private String goodsName;
// 類型,1:手機(jī),2:電腦
private int type;
@Override
public String toString() {
return goodsName;
}
}
public static void main(String[] args) {
List<Goods> list = Arrays.asList(
new Goods("iphoneX", 1)
, new Goods("mate30 pro", 1)
, new Goods("thinkpad T400", 2)
, new Goods("macbook pro", 2)
);
// 手機(jī)歸為一類,非手機(jī)商品歸為一類
// true -> 手機(jī)類商品
// false -> 非手機(jī)類商品
Map<Boolean, List<Goods>> goodsMap = list.stream()
.collect(
Collectors.partitioningBy(goods -> goods.getType() == 1)
);
// 獲取手機(jī)類商品
List<Goods> mobileGoods = goodsMap.get(true);
System.out.println(mobileGoods);
}
partitioningBy(Predicate, Collector)方法的第二個(gè)參數(shù)可以用來指定集合元素,默認(rèn)使用的List存放,如果要使用Set存放,可以這樣寫:
Map<Boolean, Set<Goods>> goodsMap = list.stream()
.collect(
Collectors.partitioningBy(
goods -> goods.getType() == 1
// 指定收集類型
, Collectors.toSet())
);
toList & toSet & toCollection
toList和toSet可以將Stream中的元素轉(zhuǎn)換成List、Set集合,這是用的比較多的兩個(gè)方法。
Stream<Goods> stream = Stream.of(
new Goods("iphoneX", 4000)
, new Goods("mate30 pro", 5999)
, new Goods("redmek20", 2999)
);
List<Goods> list = stream.collect(Collectors.toList());
Set<Goods> set = stream.collect(Collectors.toSet());
默認(rèn)情況下,toList返回的是ArrayList,toSet返回的是HashSet,如果要返回其它類型的集合比如LinkedList,可以使用toCollection,它可以讓開發(fā)者自己指定需要哪種集合。
LinkedList<Goods> linkedList = stream.collect(Collectors.toCollection(LinkedList::new));
toConcurrentMap
toConcurrentMap方法是將Stream轉(zhuǎn)換成ConcurrentMap,它由三個(gè)重載方法組成
- 重載1:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
- 重載2:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
- 重載3:toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
其中重載1調(diào)用重載2,重載2調(diào)用重載3,最終都會(huì)執(zhí)行到重載3方法上來。
先看重載1,提供了兩個(gè)參數(shù)
- keyMapper:指定ConcurrentMap中的key值
- valueMapper:指定key對(duì)應(yīng)的value
下面這個(gè)例子是將商品的名稱作為key,價(jià)格作為value
List<Goods> list = Arrays.asList(
new Goods("iphoneX", 4000)
, new Goods("mate30 pro", 5999)
, new Goods("redmek20", 2999)
);
ConcurrentMap<String, Integer> goodsMap = list.stream()
.collect(
Collectors.toConcurrentMap(Goods::getGoodsName, Goods::getPrice)
);
System.out.println(goodsMap);
打?。?/p>
{mate30 pro=5999, iphoneX=4000, redmek20=2999}
注意:這個(gè)方法要求key不能重復(fù),如果有重復(fù)的key,會(huì)拋IllegalStateException異常,如果有key重復(fù),需要使用toConcurrentMap(Function, Function, BinaryOperator),即重載2
再來看下重載2:toConcurrentMap(Function, Function, BinaryOperator),這個(gè)方法前兩個(gè)參數(shù)跟重載1一樣,第三個(gè)參數(shù)用來處理key沖突的情況,讓開發(fā)者選擇一個(gè)value值返回。
List<Goods> list = Arrays.asList(
new Goods("iphoneX", 4000)
, new Goods("mate30 pro", 5999)
, new Goods("mate30 pro", 6000) // 這里有兩個(gè)沖突了
, new Goods("redmek20", 2999)
);
ConcurrentMap<String, Integer> goodsMap = list.stream()
.collect(
Collectors.toConcurrentMap(Goods::getGoodsName, Goods::getPrice, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer price1, Integer price2) {
// 選擇價(jià)格貴的返回
return Math.max(price1, price2);
}
})
);
System.out.println(goodsMap);
打印:{mate30 pro=6000, iphoneX=4000, redmek20=2999}
這個(gè)例子中mate30 pro作為key重復(fù)了,在BinaryOperator中,我們選擇價(jià)格高的那一條數(shù)據(jù)返回。
最后看下重載3,相比于重載2,又多了一個(gè)參數(shù)Supplier,它可以讓開發(fā)者指定返回一種ConcurrentMap
重載2調(diào)用重載3,默認(rèn)使用的是ConcurrentMap::new。
注意:第四個(gè)參數(shù)必須是ConcurrentMap或ConcurrentMap的子類
小節(jié)
本篇主要講解了Stream.collect的用法,以及Collectors類中靜態(tài)方法的使用,在下一篇文章中,我們將詳細(xì)講解關(guān)于reduce的相關(guān)用法。
https://shimo.im/docs/gGQHg8xPC3X8cPHV/ 《2020年最新Java架構(gòu)師系統(tǒng)進(jìn)階資料免費(fèi)領(lǐng)取》,可復(fù)制鏈接后用石墨文檔 App 或小程序打開