【问题标题】:How do you group elements in a List<P> to a Map<K, List<V>> while retaining order?如何在保持顺序的同时将 List<P> 中的元素分组到 Map<K, List<V>>?
【发布时间】:2016-01-14 00:12:11
【问题描述】:

我有一个来自 Google Places API 的 Google PlaceSummary 对象列表。我想按他们的 Google Place ID 收集和分组它们,但也保留元素的顺序。我认为可行的是:

Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
            places.stream()
                  .collect(Collectors.groupingBy(
                          PlaceSummary::getPlaceId,
                          LinkedHashMap::new,
                          Collectors.mapping(PlaceSummary::getPlaceId, toList())
                  ));

但它甚至不会编译。看起来应该根据Java API documentation on Collectors.

以前我有这个代码:

    Map<String, List<PlaceSummary>> placesGroupedByPlaceId = places.stream()
            .collect(Collectors.groupingBy(PlaceSummary::getPlaceId));

但是,Streams API 上的标准 .collect() 不保留后续 HashMap 中的元素顺序(显然,因为 HashMaps 是无序的)。我希望输出是LinkedHashMap,以便地图按每个桶的插入顺序排序。

但是,我建议的解决方案无法编译。首先,它不能识别PlaceSummary::getPlaceId,因为它说它不是一个函数——尽管我知道它是。其次,它说我不能将LinkedHashMap&lt;Object, Object&gt; 转换为 M。M 应该是一个通用集合,所以它应该被接受。

如何使用 Java Stream API 将 List 转换为 LinkedHashMap?有没有简洁的方法来做到这一点?如果太难理解,我可能会求助于 Java 8 之前的老式方法。

我注意到有 another Stack Overflow answer on converting List to LinkedHashMap,但这没有我想要的解决方案,因为我需要收集“这个”我专门迭代的对象。

【问题讨论】:

  • docs.oracle.com/javase/8/docs/api/java/util/stream/… 中给出的示例看来,它应该可以编译。可能是Collectors.toList() 而不是toList(),除非你是静态导入的?
  • @njzk2 我不认为 toList 是问题 - 正如你所说,这是静态导入的。 LinkedHashMap::new 行在 IntelliJ 中有以下错误:方法引用中的错误返回类型:无法将 java.util.LinkedHashMap 转换为 M
  • 映射收集器应用于这些值,因此您需要 p -> p 而不是 PlaceSummary::getPlaceId
  • @AlexisC。所以那将是 p -> p.getPlaceId ?所以...这是否意味着我可以只使用 p -> p 来指代“this”元素?
  • No p -> p.getPlaceId 表示 PlaceSummary 分组值将通过其地点 ID 进行映射。如果你想要实例本身,只需应用标识函数 p -> p。

标签: java java-8 java-stream linkedhashmap collectors


【解决方案1】:

如果您需要在维护订单的同时进行分组并应用功能(减少),也许我会使用类似的东西。

final Map<Integer,Long>map=stream.collect(Collectors.groupingBy(function
   ,LinkedHashMap::new
   ,Collectors.collectingAndThen(Collectors.counting(),Function.identity()))
 )

【讨论】:

    【解决方案2】:
    /**
     * I have written this code more generic, if you want then you can group based on any * 
     * instance variable , id, name etc via passing method reference.
    **/
    
    class Student {
        private int id;
        private String name;
        public Student(int id, String name) {this.id = id;this.name = name;}
        /**
         * @return the id
         */
        public int getId() {return id;}
        /**
         * @param id
         *            the id to set
         */
        public void setId(int id) {this.id = id;}
        /**
         * @return the name
         */
        public String getName() {return name;}
        /**
         * @param name
         *            the name to set
         */
        public void setName(String name) {this.name = name;}
    }
    
    public class StudentMain {
    
        public static void main(String[] args) {
    
            List<Student> list = new ArrayList<>();
            list.add(new Student(1, "Amit"));
            list.add(new Student(2, "Sumit"));
            list.add(new Student(1, "Ram"));
            list.add(new Student(2, "Shyam"));
            list.add(new Student(3, "Amit"));
            list.add(new Student(4, "Pankaj"));
    
            Map<?, List<Student>> studentById = groupByStudentId(list,
                    Student::getId);
            System.out.println(studentById);
    
           Map<?, List<Student>> studentByName = groupByStudentId(list,
                    Student::getName);
            System.out.println(studentByName);
    
        }
    
        private static <K, V> Map<?, List<V>> groupByStudentId(List<V> list,
                Function<V, K> keyFunction) {
            return list.stream().collect(
                    Collectors.groupingBy(keyFunction, HashMap::new,
                            Collectors.toList()));
        }
    }
    

    【讨论】:

      【解决方案3】:

      你真的很接近你想要的:

      Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
                  places.stream()
                        .collect(Collectors.groupingBy(
                                PlaceSummary::getPlaceId,
                                LinkedHashMap::new,
                                Collectors.mapping(Function.identity(), Collectors.toList())
                        ));
      

      Collectors.mapping 方法中,您需要提供PlaceSummary 实例而不是地点ID。在上面的代码中,我使用了Function.identity():这个收集器用于构建值,因此我们需要累积这些位置本身(而不是它们的 ID)。

      注意可以直接写Collectors.toList()而不是Collectors.mapping(Function.identity(), Collectors.toList())

      到目前为止,您的代码无法编译,因为它实际上是在创建 Map&lt;String, List&lt;String&gt;&gt;:您正在为每个 ID 累积 ID(这很奇怪)。


      你可以把它写成一个通用方法:

      private static <K, V> Map<K, List<V>> groupByOrdered(List<V> list, Function<V, K> keyFunction) {
          return list.stream()
                      .collect(Collectors.groupingBy(
                          keyFunction,
                          LinkedHashMap::new,
                          Collectors.toList()
                      ));
      }
      

      并像这样使用它:

      Map<String, List<PlaceSummary>> placesGroupedById = groupByOrdered(places, PlaceSummary::getPlaceId);
      

      【讨论】:

      • 在生产代码中试用过 - 就像做梦一样谢谢。绝对是对 API 的误解。
      • 此外,我更喜欢 placeSummary -> placeSummary 到 Function.identity() 因为它似乎更清楚地澄清了。如果您了解它的目的,则该代码不会太奇怪。我试图做的是对重复的 ID 进行分组,然后使用规则过滤掉重复的 ID。其中一项规则是根据其是否为扩充地点(我们扩充地点以向他们提供 Google API 不知道的额外信息)保留其中一个重复项。
      【解决方案4】:

      我想你对最终的收集器有点困惑。它仅表示每个映射值中需要包含的内容。不需要第二个 mapping 收集器,因为您只需要原始对象的列表。

          Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
                places.stream()
                      .collect(Collectors.groupingBy(PlaceSummary::getPlaceId,
                                                     LinkedHashMap::new,
                                                     Collectors.toList()));
      

      【讨论】:

      • 是的,我认为这让我有点困惑。我认为如果我说实话,最好有参数 order 键、值映射、toList() - 但小抱怨。会记住这一点以供将来参考...谢谢。
      猜你喜欢
      • 1970-01-01
      • 2019-01-23
      • 1970-01-01
      • 2013-12-20
      • 1970-01-01
      • 2018-11-23
      • 2014-02-09
      • 2022-01-05
      • 2020-04-08
      相关资源
      最近更新 更多