在一塊兒來學Java8(七)——Stream(上)
中咱們瞭解到了Stream對象的經常使用方法以及用法。如今一塊兒來深刻了解下Stream.collect()
方法的使用java
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對象中。app
查看源碼發現,collect方法由兩個重載方法組成。iphone
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
<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對應的實現。ide
咱們能夠先從collect(Collector<? super T, A, R> collector)
開始入手,經過這個再去慢慢了解方法1的用法。函數
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 |
下面依次來說解下每一個方法的用處。code
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);
與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()返回集合中元素個數。
long count = Stream.of(1,2,3,4,5) .collect(Collectors.counting()); System.out.println("count:" + count); // 5
上面講到了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()是對集合中的元素進行分組,由三個重載方法組成
其中重載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方法能夠把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);
@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方法表示分區,它將根據條件將Stream中的元素分紅兩部分,並分別放入到一個Map當中,Map的key爲Boolean類型,key爲true部分存放知足條件的元素,key爲false存放不知足條件的元素。
{ true -> 符合條件的元素 false -> 不符合條件的元素 }
partitioningBy方法由兩個重載方法組成
其中重載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能夠將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方法是將Stream轉換成ConcurrentMap,它由三個重載方法組成
toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
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,提供了兩個參數
下面這個例子是將商品的名稱做爲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
的相關用法。
按期分享技術乾貨,一塊兒學習,一塊兒進步!微信公衆號:猿敲月下碼