![](/img/trans.png)
[英]Java 8 Streams - Map Multiple Object of same type to a list using streams
[英]Java 8 Streams: Map the same object multiple times based on different properties
我的一位同事向我提出了一个有趣的问题,我无法找到一个整洁而漂亮的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:创建映射条目和流它们,然后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 :这是另一个我提出的“最漂亮”的代码到目前为止但是它使用了三个arg版本的reduce
而第三个参数有点狡猾,就像在这个问题中找到的那样: 第三个参数的目的是'减少'功能在Java 8函数式编程中 。 此外, 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(超级Mac),Puma = Customer(Johnny Puma)}
{Super = Customer(Super Mac),Johnny = Customer(Johnny Puma),Mac = Customer(超级Mac),Puma = Customer(Johnny Puma)}
{Super = Customer(Super Mac),Johnny = Customer(Johnny Puma),Mac = Customer(超级Mac),Puma = Customer(Johnny Puma)}
那么,现在我的问题是:我应该如何以Java 8有序的方式流式传输List<Customer>
,然后以某种方式将其收集为Map<String, Customer>
,其中您将整个事物拆分为两个键( first
AND last
)即Customer
被映射两次。 我不想使用任何第三方库,我不想像在alt 1中那样使用流之外的地图。还有其他不错的选择吗?
我认为您的替代品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的巨大优势。 这为您提供了极大的灵活性。 例如,如果要更改备选2以覆盖重复键上的条目而不是抛出异常,则只需将(a,b) -> b
到toMap(...)
。 如果你决定要收集匹配的条目到列表中,您所要做的就是更换toMap(...)
与groupingBy(...)
等等。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.