我的一个同事向我提出了一个有趣的问题,我无法找到一个整洁漂亮的Java8解决方案。问题是流式传输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 + ")";
}
}
将其设置为列表
// The list of customers
List<Customer> customers = Arrays.asList(
new Customer("Johnny", "Puma"),
new Customer("Super", "Mac"));
备选方案1:在“流”之外(或者更确切地说在for每
之外)使用Map
。
// 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:创建地图条目并流式传输它们,然后平面图
它们。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:这是我想出的另一个到目前为止“最漂亮”的代码,但它使用了duce
的三参数版本,第三个参数有点不可靠,如本问题所示:在Java8函数式编程中,第三个参数用于“duce”函数的目的。此外,duce
似乎不适合这个问题,因为它正在发生变化,并行流可能不适用于下面的方法。
// 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=客户(Super Mac), Johnny=客户(Johnny Puma),Mac=客户(Super Mac),彪马=客户(Johnny Puma)}
{Super=客户(Super Mac),Johnny=客户(Johnny Puma),Mac=客户(Super Mac),彪马=客户(Johnny Puma)}
{Super=客户(Super Mac),Johnny=客户(Johnny Puma),Mac=客户(Super Mac),彪马=客户(Johnny Puma)}
所以,现在我的问题是:我应该如何以Java8有序的方式流式传输List
完整的代码可以在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滥用duce
。要进行可变减少,请使用收集
:
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有一个巨大的优势,它将条目的生产从它们的聚合中分离出来。这给了你很大的灵活性。例如,如果你想改变备选方案2来覆盖重复键上的条目,而不是抛出异常,你只需添加(a, b)-