【问题标题】:Java streams: groupingBy and flatMapping keysJava 流:groupingBy 和 flatMapping 键
【发布时间】:2019-08-17 00:38:35
【问题描述】:

假设我有一个国家对象列表,其中包含该国家/地区使用的语言列表,如下所示:

class Country {
    List<String> languages; 
}

我想创建以下格式的地图: Map&lt;String, List&lt;Country&gt;&gt;,这样每种语言都映射到 Country 对象列表。例如:

"French" -> [Country:France, Country:Canada],
"English" -> [Country:UK, Country:US]

性能是这里的一个问题,所以我想避免多次迭代和查找。我已经尝试使用groupingBy,但是我如何flatMap 键集?

例如,这会导致Map&lt;List&lt;String&gt;, List&lt;Country&gt;&gt;

countries.stream()
    .collect(Collectors.groupingBy(country -> country.getLanguages(), toList()));

【问题讨论】:

    标签: java java-stream flatmap


    【解决方案1】:

    由于您似乎关心性能,因此不要将流用于这个简单的任务:

    Map<String, List<Country>> countriesByLanguage = new HashMap<>();
    for (Country country : countries) {
        for (String language : country.getLanguages()) {
            countriesByLanguage.computeIfAbsent(language, k -> new ArrayList<>())
                               .add(country);
        }
    }
    

    【讨论】:

    • 如果我使用大型数据集一次性调用它,流会不会更快?只有当我多次调用它时才会更慢,因为它有更多的开销?
    • @Edwin 为什么您认为流比普通循环更快?流的开销会使它变慢。在这种特殊情况下,流式传输需要创建中间对象(例如SimpleEntry),因此会导致产生垃圾,这也是一个考虑因素。最重要的是,我发现这里的非流解决方案更简单。所以,仅仅因为你可以使用流,并不意味着你应该。流很棒,但它们并不是所有问题的答案。
    • 您介意详细说明 computeIfAbsent 的使用吗?如果存在键,这里会发生什么,它是如何添加到列表中的?
    • @Edwin 为什么我需要详细说明? 文档还不够吗? javadoc?
    • 我最初将它读作:countriesByLanguage.computeIfAbsent(language, k -&gt; new ArrayList&lt;&gt;().add(country)); 这相当令人困惑,但这显然是最好的解决方案,谢谢。
    【解决方案2】:

    您可以使用 流中的流 来做到这一点,如下所示:首先遍历国家列表,然后遍历嵌套的语言列表并准备 «language, country» 对,然后收集他们映射:

    public static void main(String[] args) {
        List<Country> countries = List.of(
                new Country("France", List.of("French")),
                new Country("Canada", List.of("French")),
                new Country("UK", List.of("English")),
                new Country("US", List.of("English")));
    
        Map<String, List<Country>> map = countries.stream()
                // Stream<Map.Entry<String,Country>>
                .flatMap(country -> country.getLanguages().stream()
                        .map(lang -> Map.entry(lang, country)))
                .collect(Collectors.toMap(
                        // key - language
                        Map.Entry::getKey,
                        // value - List<Country>
                        entry -> new ArrayList<>(List.of(entry.getValue())),
                        // merge duplicates, if any
                        (list1, list2) -> {
                            list1.addAll(list2);
                            return list1;
                        }
                ));
    
        // output
        map.forEach((k, v) -> System.out.println(k + "=" + v));
        //English=[Country:UK, Country:US]
        //French=[Country:France, Country:Canada]
    }
    
    static class Country {
        String name;
        List<String> languages;
    
        public Country(String name, List<String> languages) {
            this.name = name;
            this.languages = languages;
        }
    
        public List<String> getLanguages() {
            return languages;
        }
    
        @Override
        public String toString() {
            return "Country:" + name;
        }
    }
    

    【讨论】:

      【解决方案3】:

      这样就可以了:

      countries.stream()
              .flatMap(country -> country.getLanguages()
                      .stream()
                      .map(lang -> new SimpleEntry<>(lang,
                              new ArrayList<>(Arrays.asList(country)))))
              .collect(Collectors.toMap(
                      Entry::getKey,
                      Entry::getValue,
                      (l1, l2) -> {
                          l1.addAll(l2);
                          return l2;
                      }));
      

      【讨论】:

        猜你喜欢
        • 2021-01-27
        • 1970-01-01
        • 2022-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-01
        • 1970-01-01
        • 2020-04-22
        相关资源
        最近更新 更多