【问题标题】:Elegantly create map with object fields as key/value from object stream in Java 8在 Java 8 中以对象字段作为键/值从对象流中优雅地创建映射
【发布时间】:2017-03-23 04:58:27
【问题描述】:

我有以下课程

class Person {
    public String name;
    public int age;
    public List<String> hobbies;

    Person(String name, int age, List<String> hobbies)
    {this.name = name; this.age = age; this.hobbies = hobbies;}
}

我如何创建一个年龄映射到诸如Map&lt;Integer, Set&lt;String&gt;&gt; 之类的爱好?

我编写的 Java 8 方法是:

Map<Integer, Set<String>> collect8 = persons.stream()
    .collect(
        toMap(
            p -> p.age,
            p -> p.hobbies.stream().collect(toSet()),
            (hobbies1, hobbies2) ->
                Stream.concat(hobbies1.stream(), hobbies2.stream()).collect(toSet())
        )
     );

有没有更惯用的方式来使用Collectors.groupingBy() 呢?

作为一个相关问题,我发现没有 Java 流的版本更具可读性。

Map<Integer, Set<String>> collect7 = new HashMap<>();

for(Person p: persons) {
    Set<String> hobbies = collect7.getOrDefault(p.age, new HashSet<>());
    hobbies.addAll(p.hobbies);
    collect7.put(p.age, hobbies);
}

如果更容易阅读,我们是否应该使用非流代码?特别是当流式版本(如此处所示)没有带有数据转换的中间流但很快以终端操作结束时?

【问题讨论】:

  • 永远不要仅仅因为它们花哨而使用流——如果命令式版本看起来更容易阅读,坚持下去!流是一种工具,因此它们对一些问题有用,而不是全部。

标签: java java-8 java-stream collectors


【解决方案1】:

查看 Java 8 Collectors 文档中的类似示例:

Map<City, Set<String>> namesByCity
     = people.stream().collect(groupingBy(Person::getCity, TreeMap::new,                                           
           mapping(Person::getLastName, toSet())));

您可以在这里使用相同的方法。

【讨论】:

    【解决方案2】:

    正如您自己指出的那样:Stream 解决方案可能不像您当前的非Stream-解决方案那样可读。使用groupingBy 解决问题可能看起来不如您预期的那么好,因为您想将List 转换为Set

    我用groupingBymappingreducing 构建了一个解决方案,但该解决方案并不那么容易阅读,甚至包含一个错误。您可以在以下位置阅读更多相关信息:Java 8 stream.collect( ... groupingBy ( ... mapping( ... reducing ))) reducing BinaryOperator-usage 我真的建议查看 Holger 给出的答案,因为它还包含一个使用自定义 Collector 的更简单的解决方案以及 Java 9 的 flatMapping 的一点 Outlook,这对我来说很接近到您的非Stream-解决方案。

    但我想出的另一个使用groupingBy 的解决方案实际上是这样的:

    Map<Integer, Set<String>> yourmap;
    yourmap = personList.stream()
                        .flatMap(p -> p.hobbies.stream()
                                               .flatMap(hobby -> Stream.of(new SimpleEntry<>(p.age, hobby)))
                                )
                        .collect(Collectors.groupingBy(Entry::getKey,
                                 Collectors.mapping(Entry::getValue, Collectors.toSet())));
    

    为此,您需要以下导入:

    import java.util.AbstractMap.SimpleEntry;
    import java.util.Map.Entry;
    

    当然你也可以选择TuplePair或者你最喜欢的。

    但在任何方面都不是更好。

    我会继续使用您当前的非Stream-解决方案。它更具可读性,可以做它应该做的事情。

    【讨论】:

    • 您不能使用(strings, strings2) -&gt; {strings.addAll(strings2); return strings;},因为addAll() 返回boolean。您必须创建一个更精细的 lambda,它实际上返回一个 List
    • 其实这不是问题,因为我退回了其中一套。但是我错误地将运算符视为组合器,但事实并非如此。如果您也对这个问题感兴趣,您可以查找 the question 或直接跳转到 Holger's answer,顺便提一下,其中还包含两个很好的问题示例。
    猜你喜欢
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-24
    • 1970-01-01
    • 2019-01-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多