Java 8系列收集器Collector与工具类Collectors

Posted 善良勤劳勇敢而又聪明的老杨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 8系列收集器Collector与工具类Collectors相关的知识,希望对你有一定的参考价值。

热门系列:


 

目录

1.前言

2.正文

2.1 Collector的接口结构

2.2 Collector处理流程

2.3 Collector常用方法

2.3.1 收集

2.3.2 求值

2.3.3 分组

2.3.4 连接

2.3.5 归约

2.3.6 collectingAndThen

3.总结


1.前言

好久没有输出啦。今天接着前面说到的Java8系列,聊一聊最后的收集器:Collector !

Collector作为收集器,简单来说就是将数据或元素收集到一起!下面,就具体讲一讲有哪些收集方式!


2.正文

其实目前,我们主要用到Collector的地方,也就是与Stream(【Java 8系列】Stream详解,看这一篇就够啦)来结合使用的。所以,接下来也主要围绕我们平时可能会用到的一些常用方式,展开讲解!

2.1 Collector的接口结构

直接看源码:

public interface Collector<T, A, R> 
    // supplier参数用于生成结果容器,容器类型为A
    Supplier<A> supplier();
    // accumulator用于消费元素,也就是归纳元素,这里的T就是元素,它会将流中的元素一个一个与结果容器A发生操作
    BiConsumer<A, T> accumulator();
    // combiner用于两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果A
    BinaryOperator<A> combiner();
    // finisher用于将之前整合完的结果R转换成为A
    Function<A, R> finisher();
    // characteristics表示当前Collector的特征值,这是个不可变Set
    Set<Characteristics> characteristics();


    // 四参方法,用于生成一个Collector,T代表流中的一个一个元素,R代表最终的结果
    public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
                                              BiConsumer<R, T> accumulator,
                                              BinaryOperator<R> combiner,
                                              Characteristics... characteristics) /*...*/
    // 五参方法,用于生成一个Collector,T代表流中的一个一个元素,A代表中间结果,R代表最终结果,finisher用于将A转换为R                                          
    public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
                                                 BiConsumer<A, T> accumulator,
                                                 BinaryOperator<A> combiner,
                                                 Function<A, R> finisher,
                                                 Characteristics... characteristics) /*...*/                                           

上面代码中,已经加入了具体的注释,可以说是一目了然!下面针对源码的中的几个泛型,大致的做下了解(这里贴一下Java API文档 ,有兴趣的可以对照着理解):

文档的原文如下:

Interface Collector<T,A,R>

  • Type Parameters:

    T - the type of input elements to the reduction operation

    A - the mutable accumulation type of the reduction operation (often hidden as an implementation detail)

    R - the result type of the reduction operation

翻译过来如下(有道硬翻的,八九不离十,哈哈~~~):

接口收集器< T, A,R >

类型参数:

T -约简操作的输入元素的类型

A -约简操作的可变积累类型(通常隐藏为实现细节)

R -还原操作的结果类型

Collector中还定义了一个枚举类Characteristics,有三个枚举值,理解这三个值的含义对于我们自己编写正确的收集器也是至关重要的。

  • Characteristics.CONCURRENT:表示中间结果只有一个,即使在并行流的情况下。所以只有在并行流且收集器不具备CONCURRENT特性时,combiner方法返回的lambda表达式才会执行(中间结果容器只有一个就无需合并)。
  • Characteristics.UNORDER:表示流中的元素无序。
  • Characteristics.IDENTITY_FINISH:表示中间结果容器类型与最终结果类型一致,此时finiser方法不会被调用。

2.2 Collector处理流程

个人比较喜欢用图做理解,因为比文字更明了易于理解。下面看下其实现流程图:

 

2.3 Collector常用方法

下面具体讲解一下一些常用的方法,这些方法主要由工具类Collectors提供。一般常用方法分类主要分为:收集、求值、分组、连接、归约等

Collector方法分类概览表
收集toCollection()、toConcurrentMap()、toList()、toMap()、toSet()
求值averagingDouble()、averagingInt()、averagingLong()、counting()、maxBy()、summarizingDouble()、summarizingInt()、summarizingLong()、summingDouble()、summingInt()、summingLong()
分组groupingBy()、groupingByConcurrent()、partitioningBy()
连接joining()
归约reducing()
其他collectingAndThen()

2.3.1 收集

主要方法有:

Modifier and TypeMethod and Description
static <T,C extends Collection<T>>
Collector<T,?,C>
toCollection(Supplier<C> collectionFactory)

Returns a Collector that accumulates the input elements into a new Collection, in encounter order.

static <T,K,U> Collector<T,?,ConcurrentMap<K,U>>toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)

Returns a concurrent Collector that accumulates elements into a ConcurrentMap whose keys and values are the result of applying the provided mapping functions to the input elements.

static <T,K,U> Collector<T,?,ConcurrentMap<K,U>>toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)

Returns a concurrent Collector that accumulates elements into a ConcurrentMap whose keys and values are the result of applying the provided mapping functions to the input elements.

static <T,K,U,M extends ConcurrentMap<K,U>>
Collector<T,?,M>
toConcurrentMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)

Returns a concurrent Collector that accumulates elements into a ConcurrentMap whose keys and values are the result of applying the provided mapping functions to the input elements.

static <T> Collector<T,?,List<T>>toList()

Returns a Collector that accumulates the input elements into a new List.

static <T,K,U> Collector<T,?,Map<K,U>>toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)

Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements.

static <T,K,U> Collector<T,?,Map<K,U>>toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction)

Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements.

static <T,K,U,M extends Map<K,U>>
Collector<T,?,M>
toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)

Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements.

static <T> Collector<T,?,Set<T>>toSet()

Returns a Collector that accumulates the input elements into a new Set.

下面举例看看效果:

public static void main(String[] args) 
	List<Integer> list = Arrays.asList(1,3,8,5,2,9,4,7,6);
	list = list.stream().sorted().collect(Collectors.toList());
	//toSet()与此相似,只是收集到set中
	System.out.println("list = "+JSON.toJSONString(list));
	
	List<Integer> list1 = Arrays.asList(1,3,8,5,2,9,4,7,6);
	Map<String,Object> map = list.stream().collect(Collectors.toMap(s->s+"", s->s));
	//toConcurrentMap与此相似,只是ConcurrentMap是线程安全,一般用于并发场景
	System.out.println("map = "+JSON.toJSONString(map));

输出结果:

list = [1,2,3,4,5,6,7,8,9]
map = "1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9

 

2.3.2 求值

主要方法:

All Methods Static Methods Concrete Methods
Modifier and TypeMethod and Description
static <T> Collector<T,?,Double>averagingDouble(ToDoubleFunction<? super T> mapper)

Returns a Collector that produces the arithmetic mean of a double-valued function applied to the input elements.

static <T> Collector<T,?,Double>averagingInt(ToIntFunction<? super T> mapper)

Returns a Collector that produces the arithmetic mean of an integer-valued function applied to the input elements.

static <T> Collector<T,?,Double>averagingLong(ToLongFunction<? super T> mapper)

Returns a Collector that produces the arithmetic mean of a long-valued function applied to the input elements.

static <T> Collector<T,?,Long>counting()

Returns a Collector accepting elements of type T that counts the number of input elements.

static <T> Collector<T,?,Optional<T>>maxBy(Comparator<? super T> comparator)

Returns a Collector that produces the maximal element according to a given Comparator, described as an Optional<T>.

static <T> Collector<T,?,Optional<T>>minBy(Comparator<? super T> comparator)

Returns a Collector that produces the minimal element according to a given Comparator, described as an Optional<T>.

static <T> Collector<T,?,DoubleSummaryStatistics>summarizingDouble(ToDoubleFunction<? super T> mapper)

Returns a Collector which applies an double-producing mapping function to each input element, and returns summary statistics for the resulting values.

static <T> Collector<T,?,IntSummaryStatistics>summarizingInt(ToIntFunction<? super T> mapper)

Returns a Collector which applies an int-producing mapping function to each input element, and returns summary statistics for the resulting values.

static <T> Collector<T,?,LongSummaryStatistics>summarizingLong(ToLongFunction<? super T> mapper)

Returns a Collector which applies an long-producing mapping function to each input element, and returns summary statistics for the resulting values.

static <T> Collector<T,?,Double>summingDouble(ToDoubleFunction<? super T> mapper)

Returns a Collector that produces the sum of a double-valued function applied to the input elements.

static <T> Collector<T,?,Integer>summingInt(ToIntFunction<? super T> mapper)

Returns a Collector that produces the sum of a integer-valued function applied to the input elements.

static <T> Collector<T,?,Long>summingLong(ToLongFunction<? super T> mapper)

Returns a Collector that produces the sum of a long-valued function applied to the input elements.

实践栗子:

public static void main(String[] args) 
	List<Integer> list = Arrays.asList(1,3,8,5,2,9,4,7,6);

	//summarizingDouble、summarizingLong与此相似
	IntSummaryStatistics intSummaryStatistics = list.stream().collect(Collectors.summarizingInt(s->s));
	System.out.println("IntSummaryStatistics求和:"+intSummaryStatistics.getSum());
	System.out.println("IntSummaryStatistics求平均值:"+intSummaryStatistics.getAverage());
	System.out.println("IntSummaryStatistics求个数:"+intSummaryStatistics.getCount());
	System.out.println("IntSummaryStatistics求最大值:"+intSummaryStatistics.getMax());
	System.out.println("IntSummaryStatistics求最小值:"+intSummaryStatistics.getMin());
	System.out.println();
	
	//summingDouble、summingLong与此相似
	int i = list.stream().collect(Collectors.summingInt(s->s));
	System.out.println("summingInt求和:"+i);
	
	long num = list.stream().collect(Collectors.counting());
	System.out.println("counting求个数:"+num);
	System.out.println();
	
	Optional max = list.stream().collect(Collectors.maxBy(Comparator.comparing(s-> 
		return s;
	)));
	Optional min = list.stream().collect(Collectors.minBy(Comparator.comparing(s-> 
		return s;
	)));
	System.out.println("maxBy求最大值:"+max.get());
	System.out.println("maxBy求最小值:"+min.get());
	System.out.println();
	
	//averagingDouble、averagingLong与此相似
	double avg = list.stream().collect(Collectors.averagingInt(s->s));
	System.out.println("averagingInt求平均值:"+avg);

运行结果:

IntSummaryStatistics求和:45
IntSummaryStatistics求平均值:5.0
IntSummaryStatistics求个数:9
IntSummaryStatistics求最大值:9
IntSummaryStatistics求最小值:1

summingInt求和:45
counting求个数:9

maxBy求最大值:9
maxBy求最小值:1

averagingInt求平均值:5.0

 

2.3.3 分组

Modifier and TypeMethod and Description
static <T,K> Collector<T,?,Map<K,List<T>>>groupingBy(Function<? super T,? extends K> classifier)

Returns a Collector implementing a "group by" operation on input elements of type T, grouping elements according to a classification function, and returning the results in a Map.

static <T,K,A,D> Collector<T,?,Map<K,D>>groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)

Returns a Collector implementing a cascaded "group by" operation on input elements of type T, grouping elements according to a classification function, and then performing a reduction operation on the values associated with a given key using the specified downstream Collector.

static <T,K,D,A,M extends Map<K,D>>
Collector<T,?,M>
groupingBy(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)

Returns a Collector implementing a cascaded "group by" operation on input elements of type T, grouping elements according to a classification function, and then performing a reduction operation on the values associated with a given key using the specified downstream Collector.

static <T,K> Collector<T,?,ConcurrentMap<K,List<T>>>groupingByConcurrent(Function<? super T,? extends K> classifier)

Returns a concurrent Collector implementing a "group by" operation on input elements of type T, grouping elements according to a classification function.

static <T,K,A,D> Collector<T,?,ConcurrentMap<K,D>>groupingByConcurrent(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)

Returns a concurrent Collector implementing a cascaded "group by" operation on input elements of type T, grouping elements according to a classification function, and then performing a reduction operation on the values associated with a given key using the specified downstream Collector.

static <T,K,A,D,M extends ConcurrentMap<K,D>>
Collector<T,?,M>
groupingByConcurrent(Function<? super T,? extends K> classifier, Supplier<M> mapFactory, Collector<? super T,A,D> downstream)

Returns a concurrent Collector implementing a cascaded "group by" operation on input elements of type T, grouping elements according to a classification function, and then performing a reduction operation on the values associated with a given key using the specified downstream Collector.

static <T> Collector<T,?,Map<Boolean,List<T>>>partitioningBy(Predicate<? super T> predicate)

Returns a Collector which partitions the input elements according to a Predicate, and organizes them into a Map<Boolean, List<T>>.

static <T,D,A> Collector<T,?,Map<Boolean,D>>partitioningBy(Predicate<? super T> predicate, Collector<? super T,A,D> downstream)

Returns a Collector which partitions the input elements according to a Predicate, reduces the values in each partition according to another Collector, and organizes them into a Map<Boolean, D> whose values are the result of the downstream reduction.

groupingBy 有三种定义,上面表格有具体结构。下面用代码演示一下:

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @Author: Yangy
 * @Date: 2021/3/24 17:11
 * @Description
 */
public class CollectorDemo 
	
	public static Hero build(String name,int damage,String type)
		return new Hero(name,damage,type);
	
	
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	static class Hero
		private String name;
		private int damage;
		private String type;
	
	
	
	public static void main(String[] args) 
		
		List<Hero> heroes = Arrays.asList(CollectorDemo.build("蛮王",120,"战士"),
											CollectorDemo.build("辛德拉",60,"法师"),
											CollectorDemo.build("赵信",110,"战士"),
											CollectorDemo.build("男刀",100,"刺客"),
											CollectorDemo.build("冰霜女巫",66,"法师"),
											CollectorDemo.build("刀妹",108,"刺客"),
											CollectorDemo.build("蒙多",99,"战士"));
		
		//groupingByConcurrent与此相似,只是最终收集到ConcurrentMap中
		//按英雄类别分类,并添加到各类集合中
		Map<String,List<Hero>> heroList = heroes.stream().collect(Collectors.groupingBy(Hero::getType));
		System.out.println("分类后的英雄列表:\\n"+JSON.toJSONString(heroList));
		System.out.println();
		
		//按英雄类别分类,求各类英雄的平均战力
		Map<String,Double> damageMap = heroes.stream().collect(Collectors.groupingBy(Hero::getType,Collectors.averagingInt(Hero::getDamage)));
		System.out.println("分类后的英雄平均战力:\\n"+JSON.toJSONString(damageMap));
		System.out.println();
		
		//按英雄类别分类,求各类英雄个数,并装入指定类型的集合TreeMap
		TreeMap<String,Long> treeMap = heroes.stream().collect(Collectors.groupingBy(Hero::getType,TreeMap::new,Collectors.counting()));
		System.out.println("分类后的各类英雄个数:\\n"+JSON.toJSONString(treeMap));
		System.out.println();
	
	

输出结果如下:

分类后的英雄列表:
"刺客":["damage":100,"name":"男刀","type":"刺客","damage":108,"name":"刀妹","type":"刺客"],"法师":["damage":60,"name":"辛德拉","type":"法师","damage":66,"name":"冰霜女巫","type":"法师"],"战士":["damage":120,"name":"蛮王","type":"战士","damage":110,"name":"赵信","type":"战士","damage":99,"name":"蒙多","type":"战士"]

分类后的英雄平均战力:
"刺客":104.0,"法师":63.0,"战士":109.66666666666667

分类后的各类英雄个数:
"刺客":2,"战士":3,"法师":2

partitioningBy(分区)与 groupingBy 用法类似,唯一不同是第一个参数:partitioningBy第一个入参是一个判断表达式,返回boolean值 !

//分区实现:挑选出英雄类型为法师的对象,分为true和false两类
Map<Boolean,List<Hero>> partitionMap = heroes.stream().collect(Collectors.partitioningBy(s->s.getType().equals("法师")));
System.out.println("法师为true,其他为false:\\n"+JSON.toJSONString(partitionMap));


//输出结果
法师为true,其他为false:
false:["damage":120,"name":"蛮王","type":"战士","damage":110,"name":"赵信","type":"战士","damage":100,"name":"男刀","type":"刺客","damage":108,"name":"刀妹","type":"刺客","damage":99,"name":"蒙多","type":"战士"],true:["damage":60,"name":"辛德拉","type":"法师","damage":66,"name":"冰霜女巫","type":"法师"]

 

2.3.4 连接

Modifier and TypeMethod and Description
static Collector<CharSequence,?,String>joining()

Returns a Collector that concatenates the input elements into a String, in encounter order.

static Collector<CharSequence,?,String>joining(CharSequence delimiter)

Returns a Collector that concatenates the input elements, separated by the specified delimiter, in encounter order.

static Collector<CharSequence,?,String>joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

Returns a Collector that concatenates the input elements, separated by the specified delimiter, with the specified prefix and suffix, in encounter order.

此方式可以简单的理解为 字符串拼接 ,代码演示:

//将集合内的元素转为字符串类型(如果集合本身就是装的字符串类型,则不需要;否则必须转换),然后以 - 连接
String s = list.stream().map(a->a.toString()).collect(Collectors.joining("-"));
System.out.println("连接后:"+s);

//以 - 连接,并以  开头,以  结尾连接
s = list.stream().map(a->a.toString()).collect(Collectors.joining("-","",""));
System.out.println("加首尾连接后:"+s);

输出结果:

连接后:1-3-8-5-2-9-4-7-6
加首尾连接后:1-3-8-5-2-9-4-7-6

 

2.3.5 归约

Modifier and TypeMethod and Description
static <T> Collector<T,?,Optional<T>>reducing(BinaryOperator<T> op)

Returns a Collector which performs a reduction of its input elements under a specified BinaryOperator.

static <T> Collector<T,?,T>reducing(T identity, BinaryOperator<T> op)

Returns a Collector which performs a reduction of its input elements under a specified BinaryOperator using the provided identity.

static <T,U> Collector<T,?,U>reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)

Returns a Collector which performs a reduction of its input elements under a specified mapping function and BinaryOperator.

reducing方法有三个重载方法,其实是和Stream里的三个reduce方法对应的,二者是可以替换使用的,作用完全一致,也是对流中的元素做统计归纳作用。

public final class Collectors 
    // 无初始值的情况,返回一个可以生成Optional结果的Collector
    public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) /*...*/
    // 有初始值的情况,返回一个可以直接产生结果的Collector
    public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) /*...*/
    // 有初始值,还有针对元素的处理方案mapper,生成一个可以直接产生结果的Collector,元素在执行结果操作op之前需要先执行mapper进行元素转换操作
    public static <T, U> Collector<T, ?, U> reducing(U identity,
                                    Function<? super T, ? extends U> mapper,
                                    BinaryOperator<U> op) /*...*/

演示代码可见博主的之前在Stream篇的演示:reducing归约用法举例

 

2.3.6 collectingAndThen

该方法是在归纳动作结束之后,对归纳的结果进行再处理。简言之:将第一个参数的处理结果,再传给第二个参数处理,最后输出最终结果。

Modifier and TypeMethod and Description
static <T,A,R,RR> Collector<T,A,RR>collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher)

Adapts a Collector to perform an additional finishing transformation.

演示代码:

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @Author: Yangy
 * @Date: 2021/3/24 17:11
 * @Description
 */
public class CollectorDemo 
	
	public static Hero build(String name,int damage,String type)
		return new Hero(name,damage,type);
	
	
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	static class Hero
		private String name;
		private int damage;
		private String type;
	
	
	
	public static void main(String[] args) 
	
		List<Hero> heroes = Arrays.asList(CollectorDemo.build("蛮王",120,"战士"),
											CollectorDemo.build("辛德拉",60,"法师"),
											CollectorDemo.build("赵信",110,"战士"),
											CollectorDemo.build("男刀",100,"刺客"),
											CollectorDemo.build("冰霜女巫",66,"法师"),
											CollectorDemo.build("刀妹",108,"刺客"),
											CollectorDemo.build("蒙多",99,"战士"));

		//第一个参数结果,传给第二个参数作为入参
		String result = heroes.stream().collect(Collectors.collectingAndThen(Collectors.counting(),r->"英雄总数"+r));
		System.out.println(result);
	
	

输出结果:

英雄总数7

 


3.总结

因为都是一些工具类常用操作,所以直接以代码演示的方式讲述了一遍。作用和效果都一目了然!

另外在这里,个人有一点小的建议。以前一些可能会在数据库层面用到的聚合操作,如果数据量较大时,需要优化sql的执行效率,可以考虑将此类的操作,通过JAVA8的这些新特性和功能区完成,减少数据库层面的压力!!!

最后,如果觉得这篇文章对你有所帮助,或是有疑点及发现问题,欢迎下方留言讨论。如果可以的话,请来个“一键三连”支持一下博主吧,哈哈!

以上是关于Java 8系列收集器Collector与工具类Collectors的主要内容,如果未能解决你的问题,请参考以下文章

Java 8系列Stream详解,看这一篇就够啦

Java 8系列Java开发者的判空利器 -- Optional

Java 8系列Java日期时间的新主宰者:LocalDateLocalTimeLocalDateTimeZonedDateTime

Java 8系列全网最通俗易懂的Java8版本新特性知识汇总,看完不懂你捶我

Java 8系列全网最通俗易懂的Java8版本新特性知识汇总,看完不懂你捶我

JDK1.8新特性——Collector接口和Collectors工具类