Java 8 流 : Map the same object multiple times based on different properties

标签 java lambda functional-programming java-8 collectors

我的一位同事向我提出了一个有趣的问题,但我找不到一个简洁漂亮的 Java 8 解决方案。问题是通过 POJO 列表进行流式传输,然后将它们收集到基于多个属性的映射中 - 映射导致 POJO 多次发生

想象以下 POJO:

private static class Customer {
    public String first;
    public String last;

    public Customer(String first, String last) {
        this.first = first;
        this.last = last;
    }

    public String toString() {
        return "Customer(" + first + " " + last + ")";
    }
}

将其设置为 List<Customer> :

// The list of customers
List<Customer> customers = Arrays.asList(
        new Customer("Johnny", "Puma"),
        new Customer("Super", "Mac"));

备选方案 1:使用 Map在“流”之外(或者更确切地说是在 forEach 之外)。

// Alt 1: not pretty since the resulting map is "outside" of
// the stream. If parallel streams are used it must be
// ConcurrentHashMap
Map<String, Customer> res1 = new HashMap<>();
customers.stream().forEach(c -> {
    res1.put(c.first, c);
    res1.put(c.last, c);
});

备选方案 2:创建 map 条目并流式传输它们,然后 flatMap他们。 IMO 它有点过于冗长,不太容易阅读。

// Alt 2: A bit verbose and "new AbstractMap.SimpleEntry" feels as
// a "hard" dependency to AbstractMap
Map<String, Customer> res2 =
        customers.stream()
                .map(p -> {
                    Map.Entry<String, Customer> firstEntry = new AbstractMap.SimpleEntry<>(p.first, p);
                    Map.Entry<String, Customer> lastEntry = new AbstractMap.SimpleEntry<>(p.last, p);
                    return Stream.of(firstEntry, lastEntry);
                })
                .flatMap(Function.identity())
                .collect(Collectors.toMap(
                        Map.Entry::getKey, Map.Entry::getValue));

替代方案 3:这是迄今为止我想出的另一个“最漂亮”的代码,但它使用了 reduce 的三参数版本并且第三个参数在这个问题中发现有点狡猾:Purpose of third argument to 'reduce' function in Java 8 functional programming .此外,reduce似乎不太适合这个问题,因为它正在发生变异,并且并行流可能不适用于下面的方法。

// Alt 3: using reduce. Not so pretty
Map<String, Customer> res3 = customers.stream().reduce(
        new HashMap<>(),
        (m, p) -> {
            m.put(p.first, p);
            m.put(p.last, p);
            return m;
        }, (m1, m2) -> m2 /* <- NOT USED UNLESS PARALLEL */);

如果上面的代码是这样打印的:

System.out.println(res1);
System.out.println(res2);
System.out.println(res3);

结果是:

{Super=Customer(Super Mac), Johnny=Customer(Johnny Puma), Mac=Customer(Super Mac), Puma=Customer(Johnny Puma)}
{Super=Customer(Super Mac), Johnny=Customer(Johnny Puma), Mac=Customer(Super Mac), Puma=Customer(Johnny Puma)}
{Super=Customer(Super Mac), Johnny=Customer(Johnny Puma), Mac=Customer(Super Mac), Puma=Customer(Johnny Puma)}

所以,现在我的问题是:我应该如何以 Java 8 有序的方式流过 List<Customer>然后以某种方式将其收集为 Map<String, Customer>您将整个内容拆分为两个键( firstlast ),即 Customer被映射两次。我不想使用任何第 3 方库,也不想像 alt 1 那样在流之外使用 map 。还有其他不错的选择吗?

完整代码可以是found on hastebin进行简单的复制粘贴以使整个程序运行。

最佳答案

我认为您的备选方案 2 和 3 可以重写以更清晰:

备选方案 2:

Map<String, Customer> res2 = customers.stream()
    .flatMap(
        c -> Stream.of(c.first, c.last)
        .map(k -> new AbstractMap.SimpleImmutableEntry<>(k, c))
    ).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));

替代方案 3:您的代码通过改变 HashMap 来滥用 reduce。要进行可变归约,请使用 collect:

Map<String, Customer> res3 = customers.stream()
    .collect(
        HashMap::new, 
        (m,c) -> {m.put(c.first, c); m.put(c.last, c);}, 
        HashMap::putAll
    );

请注意,它们并不相同。如果有重复的键,替代 2 将引发异常,而替代 3 将静默覆盖条目。

如果您想要在重复键的情况下覆盖条目,我个人更喜欢备选方案 3。我立即清楚它的作用。它最类似于迭代解决方案。我希望它的性能更高,因为备选方案 2 必须使用所有平面映射为每个客户进行大量分配。

但是,备选方案 2 比备选方案 3 具有巨大优势,因为它可以将条目的生成与它们的聚合分开。这为您提供了很大的灵 active 。例如,如果您想更改备选方案 2 以覆盖重复键上的条目而不是引发异常,您只需将 (a,b) -> b 添加到 toMap(... )。如果您决定要将匹配的条目收集到列表中,您只需将 toMap(...) 替换为 groupingBy(...) 等.

关于Java 8 流 : Map the same object multiple times based on different properties,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28508253/

相关文章:

java - 顶点 + Jersey + HK2 : ServiceLocator autobindings using @Contract and @Service

java - 将变量从 JFrame 传递到另一个 JFrame

functional-programming - 使用以前的值生成序列

regex - 正则表达式 f#

c++ - 我可以使用 lambda 来简化 for 循环吗

java - JPA ManyToMany 使用分组和交叉连接将数据连接在一起

java - 无法呈现请求 [/countries] 的错误页面,因为响应已提交。因此,响应可能有错误的状态代码

lambda - 使用 lambda 的方案

python - 使用字典通过分配功能将一些列添加到数据框中

c# - Lambda 表达式 : CS to VB. 网络