簡體   English   中英

Java 8 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中那樣使用流之外的地圖。還有其他不錯的選擇嗎?

完整的代碼可以在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的巨大優勢。 這為您提供了極大的靈活性。 例如,如果要更改備選2以覆蓋重復鍵上的條目而不是拋出異常,則只需將(a,b) -> btoMap(...) 如果你決定要收集匹配的條目到列表中,您所要做的就是更換toMap(...)groupingBy(...)等等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM