在一起來學(xué)Java8(七)——Stream(上)
中我們了解到了Stream對象的常用方法以及用法±酝瘢現(xiàn)在一起來深入了解下Stream.collect()
方法的使用
collect基本用法
collect意思為收集食茎,它是對Stream中的元素進(jìn)行收集和歸納损俭,返回一個新的集合對象瑞侮。先來看一個簡單例子:
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());
}
}
在這個例子中谊娇,通過map方法返回商品名稱类垫,然后把所有的商品名稱放到了List對象中汉柒。
查看源碼發(fā)現(xiàn),collect方法由兩個重載方法組成春叫。
- 方法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肩钠,這個方法可以看做是方法1的快捷方式,因為Collector中同樣提供了Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner
這三個參數(shù)暂殖,不難猜測其底層還是要用到方法1對應(yīng)的實現(xiàn)价匠。
我們可以先從collect(Collector<? super T, A, R> collector)
開始入手,通過這個再去慢慢了解方法1的用法呛每。
Collectors
Stream.collect(Collector<? super T, A, R> collector)
方法的參數(shù)Collector對象主要由Collectors
類提供踩窖。Collectors類里面包含了一系列的靜態(tài)方法,用來返回Collector對象晨横,常用的方法如下列表所示:
方法名稱 | 描述 |
---|---|
averagingXX | 求平均數(shù) |
counting | 求集合中元素個數(shù) |
groupingBy | 對集合進(jìn)行分組 |
joining | 對集合元素進(jìn)行拼接 |
mapping | 可在分組的過程中再次進(jìn)行值的映射 |
maxBy | 求最大值 |
minBy | 求最小值 |
partitioningBy | 對元素進(jìn)行分區(qū) |
reducing | 歸納 |
summarizingXX | 匯總 |
toCollection | 轉(zhuǎn)換成集合對象 |
toConcurrentMap | 轉(zhuǎn)換成ConcurrentMap |
toList | 轉(zhuǎn)換成List |
toMap | 轉(zhuǎn)換成Map |
toSet | 轉(zhuǎn)換成Set |
下面依次來講解下每個方法的用處洋腮。
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ù)是一個函數(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("商品的平均價格:" + 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);
打印:
summingInt:6.0
summingLong:61.0
summingDouble:0.6
counting()
counting()返回集合中元素個數(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ù)該怎么辦呢渤滞,可以用summarizingXX。
IntSummaryStatistics summarizingInt = Stream.of(1, 2, 3)
.collect(Collectors.summarizingInt(val -> val));
System.out.println("平均值:" + summarizingInt.getAverage());
System.out.println("總個數(shù):" + summarizingInt.getCount());
System.out.println("總和:" + summarizingInt.getSum());
System.out.println("最大值:" + summarizingInt.getMax());
System.out.println("最小值:" + summarizingInt.getMin());
打恿裥帷:
平均值:2.0
總個數(shù):3
總和:6
最大值:3
最小值:1
summarizingInt將統(tǒng)計結(jié)果放到了一個IntSummaryStatistics對象里面妄呕,在對象中可以獲取不同的統(tǒng)計信息。
groupingBy()
groupingBy()是對集合中的元素進(jìn)行分組嗽测,由三個重載方法組成
- 重載1: groupingBy(Function)
- 重載2: groupingBy(Function, Collector)
- 重載3: groupingBy(Function, Supplier, Collector)
其中重載1調(diào)用了重載2绪励,重載2調(diào)用重載3,因此最終都會執(zhí)行到重載3中來唠粥。
首先看下重載1groupingBy(Function)
的用法疏魏,這個方法默認(rèn)分組到新的List中,下面這個例子對商品類型進(jìn)行分組晤愧,同樣的類型的商品放到一個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);
});
}
打又焕濉:
類型1:[iphoneX, mate30 pro]
類型2:[thinkpad T400, macbook pro]
上面說到了groupingBy(Function)
實際上是調(diào)用了groupingBy(Function, Collector)
,其中第二個參數(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)
手動指定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
對應(yīng)classifier,Collectors.toSet()
對應(yīng)downstream。中間那個參數(shù)HashMap::new
意思很明顯了搁凸,即返回的Map的具體實現(xiàn)類是哪個媚值,如果要改成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中存放的是集合對象,如果不想要那么多屬性凌摄,只想要對象里面的商品名稱羡蛾,,也就是說我們想得到Map<Integer, List<String>>
锨亏,其中key為商品類型痴怨,value為商品名稱集合。
這個時候Collectors.mapping()
就派上用場了器予,我們使用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()方法有兩個參數(shù),第一參數(shù)指定返回的屬性乾翔,第二個參數(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); // 打用日伞:hello,world
除此之外,String
類提供了一個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()方法需要傳入一個比較器,需要根據(jù)商品的價格來比較月劈。
同理度迂,找到最便宜的商品只需把maxBy
替換成minBy
即可。
partitioningBy
partitioningBy方法表示分區(qū)艺栈,它將根據(jù)條件將Stream中的元素分成兩部分英岭,并分別放入到一個Map當(dāng)中,Map的key為Boolean類型湿右,key為true部分存放滿足條件的元素诅妹,key為false存放不滿足條件的元素。
{
true -> 符合條件的元素
false -> 不符合條件的元素
}
partitioningBy方法由兩個重載方法組成
- 重載1:partitioningBy(Predicate)
- 重載2:partitioningBy(Predicate, Collector)
其中重載1會調(diào)用重載2,因此最終還是調(diào)用了重載2方法吭狡,我們先看下重載1方法尖殃。
下面這個例子根據(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)方法的第二個參數(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集合,這是用的比較多的兩個方法挖炬。
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钓猬,它由三個重載方法組成
- 重載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撩独,最終都會執(zhí)行到重載3方法上來逗噩。
先看重載1,提供了兩個參數(shù)
- keyMapper:指定ConcurrentMap中的key值
- valueMapper:指定key對應(yīng)的value
下面這個例子是將商品的名稱作為key跌榔,價格作為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);
打右煅恪:
{mate30 pro=5999, iphoneX=4000, redmek20=2999}
注意:這個方法要求key不能重復(fù),如果有重復(fù)的key僧须,會拋IllegalStateException異常纲刀,如果有key重復(fù),需要使用toConcurrentMap(Function, Function, BinaryOperator)
担平,即重載2
再來看下重載2:toConcurrentMap(Function, Function, BinaryOperator)
示绊,這個方法前兩個參數(shù)跟重載1一樣,第三個參數(shù)用來處理key沖突的情況暂论,讓開發(fā)者選擇一個value值返回面褐。
List<Goods> list = Arrays.asList(
new Goods("iphoneX", 4000)
, new Goods("mate30 pro", 5999)
, new Goods("mate30 pro", 6000) // 這里有兩個沖突了
, 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) {
// 選擇價格貴的返回
return Math.max(price1, price2);
}
})
);
System.out.println(goodsMap);
打印:{mate30 pro=6000, iphoneX=4000, redmek20=2999}
這個例子中mate30 pro作為key重復(fù)了取胎,在BinaryOperator
中展哭,我們選擇價格高的那一條數(shù)據(jù)返回湃窍。
最后看下重載3,相比于重載2匪傍,又多了一個參數(shù)Supplier
您市,它可以讓開發(fā)者指定返回一種ConcurrentMap
重載2調(diào)用重載3,默認(rèn)使用的是ConcurrentMap::new
役衡。
注意:第四個參數(shù)必須是ConcurrentMap或ConcurrentMap的子類
小節(jié)
本篇主要講解了Stream.collect
的用法茵休,以及Collectors
類中靜態(tài)方法的使用,在下一篇文章中手蝎,我們將詳細(xì)講解關(guān)于reduce
的相關(guān)用法榕莺。
定期分享技術(shù)干貨,一起學(xué)習(xí)棵介,一起進(jìn)步帽撑!微信公眾號:猿敲月下碼