【问题标题】:How do you convert Map<String, Person> to Map<String, Integer> in java streams inside collect?如何在 collect 的 java 流中将 Map<String, Person> 转换为 Map<String, Integer>?
【发布时间】:2018-05-29 23:47:25
【问题描述】:

假设您有一个人员列表,罗伯茨、保罗斯、理查兹等,这些人按名称分组到 Map&lt;String, List&lt;Person&gt;&gt;。你想找到最年长的 Paul、Robert 等……你可以这样做:

public static void main(String... args) {
        List<Person> people = Arrays.asList(
                new Person(23, "Paul"),
                new Person(24, "Robert"),
                new Person(32, "Paul"),
                new Person(10, "Robert"),
                new Person(4, "Richard"),
                new Person(60, "Richard"),
                new Person(9, "Robert"),
                new Person(26, "Robert")
        );

        Person dummy = new Person(0, "");
        var mapping = people.stream().collect(groupingBy(Person::getName, reducing(dummy, (p1, p2) -> p1.getAge() < p2.getAge() ? p2 : p1)));
        mapping.entrySet().forEach(System.out::println);
    }

说,我想得到Map&lt;String, Integer&gt;形式的映射,而不是Map&lt;String, Person&gt;,我可以这样做:

var mapping = people.stream().collect(groupingBy(Person::getName, mapping(Person::getAge, reducing(0, (p1, p2) -> p1 < p2 ? p2 : p1))));

以上步骤为:

  • 按名称分组到Map&lt;String/*Name*/, List&lt;Person&gt;&gt;
  • List&lt;Person&gt; 映射到List&lt;Integer&gt;
  • 在这些列表中查找最大整数。

我想知道该怎么做:

  • 按名称分组到Map&lt;String, List&lt;Person&gt;&gt;
  • 在每个组名中找到年龄最大的人,得到Map&lt;String, Person&gt;
  • Map&lt;String, Person&gt; 转换为Map&lt;String, Integer&gt;。我想在 groupingBy、reduce 和 mapping 的链中完成所有这些工作。

这是“伪代码”:

var mapping = people.stream().collect(groupingBy(Person::getName, reducing(dummy, (p1, p2) -> p1.getAge() < p2.getAge() ? p2 : p1 /*, have to write some other collector factory method here*/)));

我怎样才能做到这一点?

【问题讨论】:

  • 看起来collectingAndThen是唯一的方法?我刚刚用谷歌搜索了那个方法。
  • 你真的需要3张地图吗?或者您是在问如何以Map&lt;String, Integer&gt; 结尾?
  • @shmosel 我不需要 3 张地图,请查看我的代码以查看它返回的类型。我不会在任何地方创建 3 张地图。
  • 您的细分不是很准确。这些示例实际上都没有生成任何列表。无论哪种方式,我都不明白这个问题的意义。第一种方式有什么问题?
  • @POrekhov 这个问题是关于 Stream API 的事实已经由 [java-stream] 标签给出。无需通过使用[java-8] 标记每个[java-stream] 问题来承认此API 是在Java 8 中引入的事实。特定于版本的标签用于说明您想要特定于版本的答案。由于 API 可能会发生变化,最值得注意的是,它们会得到扩展,并且使用新的 API 版本可能会有更简单的解决方案,这是一个限制。但是将 API 限制为一个版本,但在示例代码中积极使用更新的功能是令人困惑的。

标签: java java-8 java-stream


【解决方案1】:

使用3-argument version of toMap collector 更直接:

people.stream().collect(toMap(
        Person::getName, 
        Person::getAge, 
        Integer::max
    ));

【讨论】:

    【解决方案2】:

    如果你真的想使用问题陈述中提到的groupingBy,你仍然可以这样做:

    final Map<String, Integer> oldestPersonByName = people.stream()
            .collect(Collectors.groupingBy(Person::getName, Collectors.reducing(0, Person::getAge, Integer::max)));
    

    对于归约,您必须传递一个标识元素、映射函数和用于归约的二元运算符,在本例中为 max

    【讨论】:

    • 如果我们知道年龄至少为 0,我想知道 -1 是否可以被视为有效身份。
    • 这是有道理的。让我相应地改变我的答案。
    • 我并不是说 0 更好。我的问题是函数f 的标识i 必须是一个值,使得f(i, x) 为任何x 返回x。但我不确定流中是否有任何可能 x 或任何x。如果是前者,唯一有效的身份是Integer.MIN_VALUE
    • 是的,找到减少的标识元素有时很棘手,错误的标识元素会导致难以弄清楚的细微错误。
    • @shmosel 好吧,如果我们可以确定年龄总是非负数,-1 将是max 的有效身份。此外,即使由于错误而显示负年龄值,max(-1, negative number) 仍然是负数,表明存在一些错误。
    【解决方案3】:

    我认为它会像下面这样,也使用maxBy 而不是reducing

    people.stream().collect(
        groupingBy(
            Person::getName,
            collectingAndThen(
                maxBy(Comparator.comparing(Person::getAge)),
                (Optional<Person> max) -> max.get().getAge()
            )
        )
    );
    

    【讨论】:

    • 我是这么认为的,谢谢,不幸的是,当我发布此内容时,我不知道 collectAndThen。
    • 您在重复调用getAge()。在这种情况下,首先映射到年龄,然后确定最大值更有意义。 collectingAndThen(mapping(Person::getAge, maxBy(naturalOrder())), Optional::get)mapping(Person::getAge, reducing(-1, Math::max)),有方便的缩写 reducing(-1, Person::getAge, Math::max)...
    猜你喜欢
    • 1970-01-01
    • 2019-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-24
    • 1970-01-01
    • 2015-11-17
    相关资源
    最近更新 更多