在一起來學Java8(七)——Stream(上)
中我們瞭解到了Stream對象的常用方法以及用法。現在一起來深入瞭解下Stream.collect()
方法的使用
collect基本用法
collect意思爲收集,它是對Stream中的元素進行收集和歸納,返回一個新的集合對象。先來看一個簡單例子:
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對象中。
查看源碼發現,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
這三個參數,不難猜測其底層還是要用到方法1對應的實現。
我們可以先從collect(Collector<? super T, A, R> collector)
開始入手,通過這個再去慢慢了解方法1的用法。
Collectors
Stream.collect(Collector<? super T, A, R> collector)
方法的參數Collector對象主要由Collectors
類提供。Collectors類裏面包含了一系列的靜態方法,用來返回Collector對象,常用的方法如下列表所示:
方法名稱 | 描述 |
---|---|
averagingXX | 求平均數 |
counting | 求集合中元素個數 |
groupingBy | 對集合進行分組 |
joining | 對集合元素進行拼接 |
mapping | 可在分組的過程中再次進行值的映射 |
maxBy | 求最大值 |
minBy | 求最小值 |
partitioningBy | 對元素進行分區 |
reducing | 歸納 |
summarizingXX | 彙總 |
toCollection | 轉換成集合對象 |
toConcurrentMap | 轉換成ConcurrentMap |
toList | 轉換成List |
toMap | 轉換成Map |
toSet | 轉換成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);
它們的參數是一個函數式接口,可以使用Lambda表達式編寫,其中Lambda表達式中的參數爲Stream中的元素,返回的是待求平均的數值。下面這則列子是求商品的平均值:
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()返回集合中元素個數。
long count = Stream.of(1,2,3,4,5)
.collect(Collectors.counting());
System.out.println("count:" + count); // 5
summarizingXX
上面講到了averagingXX(求平均)、summingXX(求和)、counting(求總數),如果我要同時獲取這三個數該怎麼辦呢,可以用summarizingXX。
IntSummaryStatistics summarizingInt = Stream.of(1, 2, 3)
.collect(Collectors.summarizingInt(val -> val));
System.out.println("平均值:" + summarizingInt.getAverage());
System.out.println("總個數:" + summarizingInt.getCount());
System.out.println("總和:" + summarizingInt.getSum());
System.out.println("最大值:" + summarizingInt.getMax());
System.out.println("最小值:" + summarizingInt.getMin());
打印:
平均值:2.0
總個數:3
總和:6
最大值:3
最小值:1
summarizingInt將統計結果放到了一個IntSummaryStatistics對象裏面,在對象中可以獲取不同的統計信息。
groupingBy()
groupingBy()是對集合中的元素進行分組,由三個重載方法組成
- 重載1: groupingBy(Function)
- 重載2: groupingBy(Function, Collector)
- 重載3: groupingBy(Function, Supplier, Collector)
其中重載1調用了重載2,重載2調用重載3,因此最終都會執行到重載3中來。
首先看下重載1groupingBy(Function)
的用法,這個方法默認分組到新的List中,下面這個例子對商品類型進行分組,同樣的類型的商品放到一個List中。
@Data
@AllArgsConstructor
static class Goods {
private String goodsName;
// 類型,1:手機,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)
實際上是調用了groupingBy(Function, Collector)
,其中第二個參數Collector
決定了轉換到哪裏,默認是toList()
,參見groupingBy(Function)
的源碼:
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
因此我們可以調用groupingBy(Function, Collector)
手動指定Collector,假設我們要把轉換後的元素放到Set當中,可以這樣寫:
Map<Integer, Set<Goods>> goodsListMap = list.stream()
.collect(Collectors.groupingBy(Goods::getType, Collectors.toSet()));
查看重載2方法源碼,發現其調用了重載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
對應classifier,Collectors.toSet()
對應downstream。中間那個參數HashMap::new
意思很明顯了,即返回的Map的具體實現類是哪個,如果要改成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參數不僅僅只可以toList(),toSet(),它還有更加靈活的用法,之前我們轉換的都是Map<Integer, List<Goods>>
形式,value中存放的是集合對象,如果不想要那麼多屬性,只想要對象裏面的商品名稱,,也就是說我們想得到Map<Integer, List<String>>
,其中key爲商品類型,value爲商品名稱集合。
這個時候Collectors.mapping()
就派上用場了,我們使用groupingBy(Function, Collector)
方法,第二參數傳Collectors.mapping()
Map<Integer, List<String>> goodsListMap =
list.stream()
.collect(
Collectors.groupingBy(
Goods::getType,
Collectors.mapping(Goods::getGoodsName, Collectors.toList())
)
);
mapping()方法有兩個參數,第一參數指定返回的屬性,第二個參數指定返回哪種集合。
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()方法需要傳入一個比較器,需要根據商品的價格來比較。
同理,找到最便宜的商品只需把maxBy
替換成minBy
即可。
partitioningBy
partitioningBy方法表示分區,它將根據條件將Stream中的元素分成兩部分,並分別放入到一個Map當中,Map的key爲Boolean類型,key爲true部分存放滿足條件的元素,key爲false存放不滿足條件的元素。
{
true -> 符合條件的元素
false -> 不符合條件的元素
}
partitioningBy方法由兩個重載方法組成
- 重載1:partitioningBy(Predicate)
- 重載2:partitioningBy(Predicate, Collector)
其中重載1會調用重載2,因此最終還是調用了重載2方法,我們先看下重載1方法。
下面這個例子根據商品類型,將商品劃分爲手機類商品和非手機類商品。
@Data
@AllArgsConstructor
static class Goods {
private String goodsName;
// 類型,1:手機,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)
);
// 手機歸爲一類,非手機商品歸爲一類
// true -> 手機類商品
// false -> 非手機類商品
Map<Boolean, List<Goods>> goodsMap = list.stream()
.collect(
Collectors.partitioningBy(goods -> goods.getType() == 1)
);
// 獲取手機類商品
List<Goods> mobileGoods = goodsMap.get(true);
System.out.println(mobileGoods);
}
partitioningBy(Predicate, Collector)方法的第二個參數可以用來指定集合元素,默認使用的List存放,如果要使用Set存放,可以這樣寫:
Map<Boolean, Set<Goods>> goodsMap = list.stream()
.collect(
Collectors.partitioningBy(
goods -> goods.getType() == 1
// 指定收集類型
, Collectors.toSet())
);
toList & toSet & toCollection
toList和toSet可以將Stream中的元素轉換成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());
默認情況下,toList返回的是ArrayList,toSet返回的是HashSet,如果要返回其它類型的集合比如LinkedList,可以使用toCollection
,它可以讓開發者自己指定需要哪種集合。
LinkedList<Goods> linkedList = stream.collect(Collectors.toCollection(LinkedList::new));
toConcurrentMap
toConcurrentMap方法是將Stream轉換成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調用重載2,重載2調用重載3,最終都會執行到重載3方法上來。
先看重載1,提供了兩個參數
- keyMapper:指定ConcurrentMap中的key值
- valueMapper:指定key對應的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不能重複,如果有重複的key,會拋IllegalStateException異常,如果有key重複,需要使用toConcurrentMap(Function, Function, BinaryOperator)
,即重載2
再來看下重載2:toConcurrentMap(Function, Function, BinaryOperator)
,這個方法前兩個參數跟重載1一樣,第三個參數用來處理key衝突的情況,讓開發者選擇一個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重複了,在BinaryOperator
中,我們選擇價格高的那一條數據返回。
最後看下重載3,相比於重載2,又多了一個參數Supplier
,它可以讓開發者指定返回一種ConcurrentMap
重載2調用重載3,默認使用的是ConcurrentMap::new
。
注意:第四個參數必須是ConcurrentMap或ConcurrentMap的子類
小節
本篇主要講解了Stream.collect
的用法,以及Collectors
類中靜態方法的使用,在下一篇文章中,我們將詳細講解關於reduce
的相關用法。
歡迎關注作者微信公衆號:猿敲月下碼,第一時間獲得技術分享