在 一起来学Java8(七)——Stream(上)
中我们了解到了Stream对象的常用方法以及用法。现在一起来深入了解下 Stream.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方法由两个重载方法组成。
<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
这三个参数,不难猜测其底层还是要用到方法1对应的实现。
我们可以先从 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 |
下面依次来讲解下每个方法的用处。
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中来。
首先看下重载1 groupingBy(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
形式,value中存放的是集合对象,如果不想要那么多属性,只想要对象里面的商品名称,,也就是说我们想得到 Map
,其中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 mergeFunction)
toConcurrentMap(Function super T, ? extends K> keyMapper, Function super T, ? extends U> valueMapper, BinaryOperator mergeFunction, Supplier
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
的相关用法。
定期分享技术干货,一起学习,一起进步!微信公众号:猿敲月下码