提到Group By,首先想到的往往是sql中的group by操作,对搜索结果进行分组。其实Java8 Streams API中的Collector也支持流中的数据进行分组和分区操作,本片文章讲简单介绍一下,如何使用groupingBy 和 partitioningBy来对流中的元素进行分组和分区。

  • groupingBy

首先看一下Java8之前如果想对一个List做分组操作,我们需要如下代码操作:

@Test
public void groupListBeforeJava8() {
    Map<String, List<Employee>> result = new HashMap<>();
    for (Employee e : employees) {
        String city = e.getCity();
        List<Employee> empsInCity = result.get(city);
        if (empsInCity == null) {
            empsInCity = new ArrayList<>();
            result.put(city, empsInCity);
        empsInCity.add(e);
    System.out.println(result);
    assertEquals(result.get("London").size(), 2);

而如果使用Java8中Stream的groupingBy分组器,就可以这样操作:

* 使用java8 stream groupingBy操作,按城市分组list @Test public void groupingByTest() { Map<String, List<Employee>> employeesByCity = employees.stream().collect(Collectors.groupingBy(Employee::getCity)); System.out.println(employeesByCity); assertEquals(employeesByCity.get("London").size(), 2);

上面是groupingBy分组器最常见的一个用法,下面简单介绍一下其他用法:

  • 统计每个分组的count

* 使用java8 stream groupingBy操作,按城市分组list统计count @Test public void groupingByCountTest() { Map<String, Long> employeesByCity = employees.stream().collect(Collectors.groupingBy(Employee::getCity, Collectors.counting())); System.out.println(employeesByCity); assertEquals(employeesByCity.get("London").longValue(), 2L);
  • 统计分组平均值

* 使用java8 stream groupingBy操作,按城市分组list并计算分组销售平均值 @Test public void groupingByAverageTest() { Map<String, Double> employeesByCity = employees.stream().collect(Collectors.groupingBy(Employee::getCity, Collectors.averagingInt(Employee::getSales))); System.out.println(employeesByCity); assertEquals(employeesByCity.get("London").intValue(), 175);
  • 统计分组总值

* 使用java8 stream groupingBy操作,按城市分组list并计算分组销售总值 @Test public void groupingBySumTest() { Map<String, Long> employeesByCity = employees.stream().collect(Collectors.groupingBy(Employee::getCity, Collectors.summingLong(Employee::getSales))); //对Map按照分组销售总值逆序排序 Map<String, Long> finalMap = new LinkedHashMap<>(); employeesByCity.entrySet().stream() .sorted(Map.Entry.<String, Long>comparingByValue() .reversed()).forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue())); System.out.println(finalMap); assertEquals(finalMap.get("London").longValue(), 350);
  • Join分组List

* 通过type分组list,通过join操作连接分组list @Test public void groupingByConvertResultTest(){ List<BlogPost> blogPostList = Lists.newArrayList(); blogPostList.add(new BlogPost("post1", "zhuoli", 1, 30)); blogPostList.add(new BlogPost("post2", "zhuoli", 1, 40)); blogPostList.add(new BlogPost("post3", "zhuoli", 2, 15)); blogPostList.add(new BlogPost("post4", "zhuoli", 3, 33)); blogPostList.add(new BlogPost("post5", "Alice", 1, 99)); blogPostList.add(new BlogPost("post6", "Michael", 3, 65)); Map<Integer, String> postsPerType = blogPostList.stream() .collect(Collectors.groupingBy(BlogPost::getType, Collectors.mapping(BlogPost::getTitle, Collectors.joining(", ", "Post titles: [", "]")))); System.out.println(postsPerType);
  • 转换分组结果List -> List

* 使用java8 stream groupingBy操作,按城市分组list,将List转化为name的List @Test public void groupingByCityMapList(){ Map<String, List<String>> namesByCity = employees.stream().collect(Collectors.groupingBy(Employee::getCity, Collectors.mapping(Employee::getName, Collectors.toList()))); System.out.println(namesByCity); assertThat(namesByCity.get("London"), contains("Alice", "Bob"));
  • 转换分组结果List -> Set

* 使用java8 stream groupingBy操作,按城市分组list,将List转化为name的Set @Test public void groupingByCityMapListToSet(){ Map<String, Set<String>> namesByCity = employees.stream().collect(Collectors.groupingBy(Employee::getCity, Collectors.mapping(Employee::getName, Collectors.toSet()))); System.out.println(namesByCity); assertThat(namesByCity.get("London"), containsInAnyOrder("Alice", "Bob"));
  • 使用对象分组List

* 使用java8 stream groupingBy操作,通过Object对象的成员分组List @Test public void groupingByObjectTest(){ List<BlogPost> blogPostList = Lists.newArrayList(); blogPostList.add(new BlogPost("post1", "zhuoli", 1, 30)); blogPostList.add(new BlogPost("post2", "zhuoli", 1, 40)); blogPostList.add(new BlogPost("post3", "zhuoli", 2, 15)); blogPostList.add(new BlogPost("post4", "zhuoli", 3, 33)); blogPostList.add(new BlogPost("post5", "Alice", 1, 99)); blogPostList.add(new BlogPost("post6", "Michael", 3, 65)); Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = blogPostList.stream() .collect(Collectors.groupingBy(post -> new Tuple(post.getAuthor(), post.getType()))); System.out.println(postsPerTypeAndAuthor);
  • 使用两个成员分组List

* 通过author和type分组list @Test public void groupingByMultiItemTest(){ List<BlogPost> blogPostList = Lists.newArrayList(); blogPostList.add(new BlogPost("post1", "zhuoli", 1, 30)); blogPostList.add(new BlogPost("post2", "zhuoli", 1, 40)); blogPostList.add(new BlogPost("post3", "zhuoli", 2, 15)); blogPostList.add(new BlogPost("post4", "zhuoli", 3, 33)); blogPostList.add(new BlogPost("post5", "Alice", 1, 99)); blogPostList.add(new BlogPost("post6", "Michael", 3, 65)); Map<String, Map<Integer, List<BlogPost>>> map = blogPostList.stream() .collect(Collectors.groupingBy(BlogPost::getAuthor, Collectors.groupingBy(BlogPost::getType))); System.out.println(map);
  • 自定义DistinctBy对分组结果去重

使用groupingBy源于工作的一个需求,存在如下数据结构:

@Data
@AllArgsConstructor
public class TestData {
    private Integer scene;
    private Integer placement;
    private Long bid;

对TestData的List分组,统计每个sene已被占用的placement,我当时直接使用groupIngBy进行分组,得到了一个Map<Integer, List<Integer>的map,看似完成了目标需求,但当我审查结果的时候,发现List中存在重复现象。比如List<TestData>中存在多个Scene为1,placement也为1的元素,目标Map中key为1的value List中就会存在多个1,而实际上我们只需要一个1就能说明placement 1已经被占用了,所以我又希望通过distinct进行去重。但是Stream的distinct只能根据元素去重,并不能根据元素的某个成员去重,即distinctBy操作。经过一番折腾,最终实现了功能,看一下示例代码:

public class DistinctByKey {
    @Test
    public void distinctByKeyTest() {
        TestData testData1 = new TestData(1, 1, 100L);
        TestData testData2 = new TestData(1, 2, 1000L);
        TestData testData3 = new TestData(1, 3, 100L);
        TestData testData4 = new TestData(1, 1, 80L);
        TestData testData5 = new TestData(2, 1, 1600L);
        TestData testData6 = new TestData(2, 2, 1030L);
        TestData testData7 = new TestData(2, 2, 1001L);
        TestData testData8 = new TestData(2, 2, 1500L);
        TestData testData9 = new TestData(3, 5, 1500L);
        List<TestData> testDataList = Stream.of(testData1, testData2, testData3, testData4, testData5, testData6, testData7, testData8, testData9).collect(Collectors.toList());
        /*直接按照placement去重,scene为2的placement为1和2的元素被去掉*/
        List<TestData> distinctBykeyList = testDataList.stream().filter(distinctByKey(TestData::getPlacement)).collect(Collectors.toList());
        System.out.println(distinctBykeyList);
        Map<Integer, List<Integer>> resultMap = testDataList.stream().collect(Collectors.groupingBy(TestData::getScene)).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey,
                        entry -> entry.getValue().stream().filter(distinctByKey(TestData::getPlacement)).map(TestData::getPlacement).collect(Collectors.toList())));
        System.out.println(resultMap);
    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));

其实Stream的distinct方法,也是filter的一个特别实现。上述示例中distinctByKey也是通过filter,实现的,不多说,看一下示例代码就可以看明白。

示例代码:卓立 – 码云 – groupingBy操作

参考链接:

  1. Java 8 Streams API:对Stream分组和分区
  2. Java 8 – Stream Collectors groupingBy examples
提到Group By,首先想到的往往是sql中的group by操作,对搜索结果进行分组。其实Java8 Streams API中的Collector也支持流中的数据进行分组和分区操作,本片文章讲简单介绍一下,如何使用groupingBy 和 partitioningBy来对流中的元素进行分组和分区。 groupingBy 首先看一下Java8之前如果想对一个List做分组操作,我们需要...
List进行分组java8的Stream 分组groupBy 的使用) 最近在做一个功能:对一个接口接收的List数据进行校验,同一个订单里的一个产品id只能添加一次。本来想是在入库的时候通过SQL语句进行处理的。但是由于这个数据接口之前同事写了很多的校验,是在是又*又长。在度娘上查一下,发现了JAVA8 可以通过StreamList进行处理(这里主要是关于分组的); Order order1 = new Order(); order1.setOrderId("123"); Order1.
StreamList&lt;T&gt;等更多操作进行操可参考:https://blog.csdn.net/u011663149/article/details/86743930        groupingBy() 提供与SQL的GROUP BY子句类似的功能,只有Java Stream API才有。为了使用它,我们需要指定一个用于执行分组的属性。我们通过提供功能接口的实现来实...
在前面,写过java8实战系列文章,文章的内容都是基于《Java8 in Action》一书学习总结的。 这段时间在项目中很多地方都用到了,但很多时候都需要现查一下该怎么写,本篇博客就来总结一下关于List的一些常用的流操作。 学生实体代码如下: @Data public class Student implements Serializable { private Integer id;
项目场景: 前段时间遇到了一个业务场景,要对List<Object>对象列表进行较复杂的排序操作:首先要对列表根据对象属性A进行分组,然后要对分组后的每组内的对象属性B(每组的属性B值相同,可能为空)对组进行排序,然后每组组内要对属性C进行排序 这里的对象为People类属性为: @Data public class People { /**国家*/ private String country; /**国庆*/ private Date national
之前也写过很多篇关于Java8使用的文章了,但是回顾一下,好像还没介绍过Java8 Stream的flatMap操作,昨天刚好在工作中遇到一个场景,发现flatMap简直太方便了,这里总结一下flatMap的常规使用。附带讲一下,使用Java8实现集合的并、交、差操作,其实之前也讲过一种使用Guava的实现方式,具体请参考Guava集合工具 flatMap 首先看一下一种场景,存在一个M...
Java 8引入了Stream API,有许多新方法,其中有一个对于分组和聚合操作非常有用,那就是groupingBy()方法。它可以将一个流分组成一个Map,其中Entry的key是分组的条件,value是分组的结果,通常是一个List或其他集合。groupingBy()方法的另一个形式是groupingByConcurrent(),它返回一个并发Map,对于并发访问更加友好。 利用groupingBy()方法进行多字段分组和求和操作的示例如下: 假设有一个Person类,其中包含属性:name, age和salary。现在我们需要根据name和age两个字段进行分组,并求出每组的salary总和。可以使用groupingBy()方法加上summingDouble()方法来实现: List<Person> persons = Arrays.asList( new Person("Tom", 20, 5000), new Person("Tom", 21, 4000), new Person("Jerry", 22, 6000), new Person("Jerry", 23, 5500), new Person("Kate", 24, 7000), new Person("Kate", 25, 8000) Map<String, Map<Integer, Double>> result = persons.stream() .collect(Collectors.groupingBy(Person::getName, Collectors.groupingBy(Person::getAge, Collectors.summingDouble(Person::getSalary)))); 这里的personList是一个包含了6个Person对象的List,我们希望将其中相同name和age的对象分组,求得salary的总和。在groupingBy()方法中,第一个参数是分组条件,这里是Person::getName,第二个参数是分组的结果,这里是一个嵌套的groupingBy()方法,用于再次按照age进行分组,结果是一个Map<Integer, Double>。最后,我们使用summingDouble()方法对salary字段进行求和,得到各个分组的salary总和。这里的result是一个Map<String, Map<Integer, Double>>类型的对象,其中key是name,value是以age为key,salary总和为value的子Map,就是我们需要的结果。 这样,我们就利用Java 8中的Stream API和groupingBy()方法进行了多字段分组和求和操作,代码简洁,可读性强,非常方便。
一行代码能搞定的事情,没必要写个工具类 // List<Shop>转换为List<ShopQueryDto> List<Shop> shopList= getXxxList(shop); List<ShopQueryDto> list = shopList.stream().map(x -> dozer.map(x,ShopQueryDto.class)).collect(Collectors.toList()); Spring Boot + Mybatis数据源配置的三种方式 Sharon~: 同问3.3@Import注解是必须加的吗? golang websocket编程 Nihility/: 能跑起来吗 Spring Boot + Mybatis数据源配置的三种方式 征途黯然.: 感谢博主,你的文章让我得到一些收获!( ̄ˇ ̄)