在一块儿来学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
的相关用法。
按期分享技术干货,一块儿学习,一块儿进步!微信公众号:猿敲月下码