Java 8 groupingBy使用手册

作者 | 2020年5月18日

在本文中我们将使用通过多个例子来学习groupingBy的使用。

2. GroupingBy

Java 8流式API可以让我们通过声明式的方式处理数据。

静态工厂方法Collectors.groupingBy()Collectors.groupingByConcurrent()提供了类似于SQL语句中GROPU BY所实现的功能。

上述方法用于根据对象的某个属性进行分组,并把结果存放在一个Map实例中。

groupingBy函数具有以下这些重载定义:

  • 使用一个分类函数作为参数
static <T,K> Collector<T,?,Map<K,List<T>>> 
  groupingBy(Function<? super T,? extends K> classifier)
  • 使用一个分类函数与一个Collector作为参数
static <T,K,A,D> Collector<T,?,Map<K,D>>
  groupingBy(Function<? super T,? extends K> classifier, 
    Collector<? super T,A,D> downstream)
  • 使用一个分类函数、一个Supplier(用于提供用于存放结果的Map实现)以及一个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)

2.1 示例代码准备

为了方便演示groupingBy()的使用,让我们先定义一个BlogPost类:

class BlogPost {
    String title;
    String author;
    BlogPostType type;
    int likes;
}

BlogPostType类:

enum BlogPostType {
    NEWS,
    REVIEW,
    GUIDE
}

一个BlogPost列表:

List<BlogPost> posts = Arrays.asList( ... );

我们还要定义一个Tuple类,该类组合了typeauthor属性用于分类。

2.2 根据单个属性进行分组

让我们从最简单的groupingBy方法开始,它只接受一个分类函数作为参数,这个分类函数将被应用于每一个元素上面,分类函数的返回值将被当做Map中的键。

Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType));

2.3 根据对象进行分组

分类函数的返回值并没有被限定,你可以返回任何对象,只有我们确保我们正确实现了equalshashcode方法。

同时根据type与author属性分组:

Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
  .collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));

PS:确保重写了Tuple类的equalshashcode方法

2.4 修改返回的Map类型

第二个groupingBy的重载实现可以接受一个Collector作为参数。

PS:如2.2节所示,当我们只指定一个分类函数时,默认将使用toList()作为Collector。

让我们使用toSet()作为Collector来获得一个BlogPost对象的Set:

Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, toSet()));

2.5 根据多个字段进行分组

groupingBy接受的Collector也可以是另外一个groupingBy

例如,先根据author分组,再根据type分组:

Map<String, Map<BlogPostType, List>> map = posts.stream()
  .collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));

2.6 获取分组结果的平均值

我们可以把聚合函数作为参数传给groupingBy的第二个参数。

例如,获取文章类型的点赞平均数量:

Map<BlogPostType, Double> averageLikesPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));

2.7 获取分组结果的和

获取每种文章类型的点赞总数:

Map<BlogPostType, Integer> likesPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));

获取分组结果的最大值与最小值

获取每种文章类型下点赞数量最高的文章:

Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream()
  .collect(groupingBy(BlogPost::getType,
  maxBy(comparingInt(BlogPost::getLikes))));

PS:取最小值用minBy()

3. 总结

在本文中,我们已经通过几个例子学习了groupingByCollector的使用方法。

发表评论

电子邮件地址不会被公开。 必填项已用*标注