自 Java 8 推出以來,處理資料流已成為 Java 開發中的一項常見任務。通常,這些流包含複雜的結構(例如對映),這在進一步處理它們時可能會帶來挑戰。
在本教程中,我們將探討如何將地Map對映流展平為單個Map對映。
在深入研究解決方案之前,讓我們先澄清一下“展平Map流”的含義。本質上,我們希望將對映流轉換為單個Map對映,其中包含流中每個Map對映的所有鍵值對。
像往常一樣,一個例子可以幫助我們快速理解問題。假設我們有三個儲存玩家姓名和分數之間關聯的Map對映:
Map<String, Integer> playerMap1 = new HashMap<String, Integer>() {{
put("Kai", 92);
put("Liam", 100);
}};
Map<String, Integer> playerMap2 = new HashMap<String, Integer>() {{
put("Eric", 42);
put("Kevin", 77);
}};
Map<String, Integer> playerMap3 = new HashMap<String, Integer>() {{
put("Saajan", 35);
}};
我們的輸入是包含這些對映的流。為簡單起見,我們將在本教程中使用Stream.of(playerMap1, playerMap2 , …)構建輸入流。然而,值得注意的是,流不一定具有定義的遇到順序。
現在,我們的目標是將包含上述三個Map對映的流合併為一個名稱-分數Map對映:
Map<String, Integer> expectedMap = new HashMap<String, Integer>() {{
put("Saajan", 35);
put("Liam", 100);
put("Kai", 92);
put("Eric", 42);
put("Kevin", 77);
}};
值得一提的是,由於我們使用的是HashMap物件,因此無法保證最終結果中的條目順序。
此外,流中的Map對映可能包含重複的鍵和空值。稍後,我們將擴充套件示例以涵蓋本教程中的這些場景。
接下來,讓我們深入研究程式碼。
使用flatMap()和Collectors.toMap()
合併Map的一種方法是使用flatMap()方法和toMap ()收集器:
Map<String, Integer> mergedMap = Stream.of(playerMap1, playerMap2, playerMap3) |
- 在上面的程式碼中,flatMap ()方法將流中的每個對映展平為其條目流。
- 然後,我們使用toMap()收集器將流的元素收集到單個對映中。
toMap ()收集器需要兩個函式作為引數:
- 一個用於提取鍵 ( Map.Entry::getKey ),
- 另一個用於提取值 ( Map.Entry::getValue )。
這裡,我們使用方法引用來表示這兩個函式。這些函式應用於流中的每個條目以構造結果對映。
處理重複鍵
我們學習瞭如何使用toMap()收集器將HashMap流合併到一個對映中。然而,如果Map對映流包含重複的鍵,這種方法將會失敗。例如,如果我們將具有重複鍵“Kai”的新對映新增到流中,則會丟擲IllegalStateException:
Map<String, Integer> playerMap4 = new HashMap<String, Integer>() {{ |
為了解決重複鍵的問題,我們可以將合併函式作為第三個引數傳遞給toMap()方法來處理與重複鍵關聯的值。
對於重複鍵場景,我們可能有不同的合併要求。在我們的示例中,一旦出現重複名稱,我們希望選擇較高的分數。因此,我們的目標是得到這Map:
Map<String, Integer> expectedMap = new HashMap<String, Integer>() {{ |
接下來我們看看如何實現:
Map<String, Integer> mergedMap = Stream.of(playerMap1, playerMap2, playerMap3, playerMap4) |
如程式碼中所示,我們使用方法引用Integer::max作為toMap()中的合併函式。這確保了當出現重複鍵時,最終對映中的結果值將是與這些鍵關聯的兩個值中較大的一個。
處理空值
我們已經看到Collectors.toMap()可以方便地將條目收集到單個對映中。但是,Collectors.toMap ()方法無法將null處理為 map 的值。如果任何對映條目的值為null,我們的解決方案將引發NullPointerException 。
讓我們新增一個新Map來驗證:
Map<String, Integer> playerMap5 = new HashMap<String, Integer>() {{ |
現在,輸入流中的對映包含重複的鍵和空值。這一次,我們仍然希望重複的玩家名字能夠獲得更高的分數。此外,我們將null視為最低分數。 然後,我們的預期結果如下:
Map<String, Integer> expectedMap = new HashMap<String, Integer>() {{ |
由於Integer.max()無法處理null值,因此我們建立一個 null 安全方法來從兩個可為 null 的Integer物件中獲取較大的值:
private Integer maxInteger(Integer int1, Integer int2) { |
接下來我們來解決這個問題。
使用flatMap()和forEach()
解決這個問題的一個簡單方法是首先初始化一個空對映,然後在forEach()中將put()所需的鍵值對放入其中:
Map<String, Integer> mergedMap = new HashMap<>(); |
使用groupingBy()、 mapping()和reducing()
flatMap () + forEach()解決方案很簡單。然而,它不是一種函式式方法,需要我們編寫一些樣板合併邏輯。
或者,我們可以結合groupingBy()、mapping()和ducing() 收集器來從功能上解決這個問題:
Map<String, Integer> mergedMap = Stream.of(playerMap1, playerMap2, playerMap3, playerMap4, playerMap5) |
如上面的程式碼所示,我們在collect()方法中組合了三個收集器。接下來,讓我們快速瞭解一下他們是如何協同工作的:
- groupingBy(Map.Entry::getKey, mapping(…)) –按鍵對對映條目進行分組以獲取鍵 -> 條目結構,這些條目將轉到對映()
- Map(Map.Entry :: getValue,reducing(…) -使用Map.Entry :: getValue將每個 Entry對映到Integer並將Integer值移交給另一個下游收集器reducing()的下游收集器
- reduce(null, this::maxInteger) – 下游收集器透過執行maxInteger函式來應用減少重複鍵的整數值的邏輯,該函式返回兩個整數值中的最大值