簡體   English   中英

Java8:HashMap<x, y> 至 HashMap<x, z> 使用 Stream / Map-Reduce / 收集器</x,></x,>

[英]Java8: HashMap<X, Y> to HashMap<X, Z> using Stream / Map-Reduce / Collector

我知道如何從Y -> Z “轉換”一個簡單的 Java List ,即:

List<String> x;
List<Integer> y = x.stream()
        .map(s -> Integer.parseInt(s))
        .collect(Collectors.toList());

現在我想對 Map 做基本相同的事情,即:

INPUT:
{
  "key1" -> "41",    // "41" and "42"
  "key2" -> "42"      // are Strings
}

OUTPUT:
{
  "key1" -> 41,      // 41 and 42
  "key2" -> 42       // are Integers
}

解決方案不應限於String -> Integer 就像上面的List示例一樣,我想調用任何方法(或構造函數)。

Map<String, String> x;
Map<String, Integer> y =
    x.entrySet().stream()
        .collect(Collectors.toMap(
            e -> e.getKey(),
            e -> Integer.parseInt(e.getValue())
        ));

它不如列表代碼那么好。 您無法在map()調用中構造新的Map.Entry ,因此將工作混合到collect()調用中。

以下是Sotirios Delimanolis回答的一些變化,這一點非常好(+1)。 考慮以下:

static <X, Y, Z> Map<X, Z> transform(Map<? extends X, ? extends Y> input,
                                     Function<Y, Z> function) {
    return input.keySet().stream()
        .collect(Collectors.toMap(Function.identity(),
                                  key -> function.apply(input.get(key))));
}

這里有幾點。 首先是在泛型中使用通配符; 這使得功能更加靈活。 例如,如果您希望輸出映射具有輸入映射鍵的超類的鍵,則需要使用通配符:

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<CharSequence, Integer> output = transform(input, Integer::parseInt);

(地圖的值也有一個例子,但它確實是人為的,我承認Y的有界通配符只對邊緣情況有幫助。)

第二點是,不是在輸入映射的entrySet上運行流,而是在keySet運行它。 我認為,這使得代碼更加清晰,代價是必須從地圖而不是地圖條目中獲取值。 順便說一句,我最初使用key -> key作為toMap()的第一個參數,並且由於某種原因,它因類型推斷錯誤而失敗。 將其更改為(X key) -> key工作,與Function.identity()

還有另一種變化如下:

static <X, Y, Z> Map<X, Z> transform1(Map<? extends X, ? extends Y> input,
                                      Function<Y, Z> function) {
    Map<X, Z> result = new HashMap<>();
    input.forEach((k, v) -> result.put(k, function.apply(v)));
    return result;
}

這使用Map.forEach()而不是stream。 我認為這更簡單,因為它省去了收藏家,這些收藏家與地圖一起使用有些笨拙。 原因是Map.forEach()將鍵和值作為單獨的參數給出,而流只有一個值 - 您必須選擇是使用鍵還是映射條目作為該值。 從缺點來看,這缺乏其他方法的豐富,優雅的優點。 :-)

像這樣的通用解決方案

public static <X, Y, Z> Map<X, Z> transform(Map<X, Y> input,
        Function<Y, Z> function) {
    return input
            .entrySet()
            .stream()
            .collect(
                    Collectors.toMap((entry) -> entry.getKey(),
                            (entry) -> function.apply(entry.getValue())));
}

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<String, Integer> output = transform(input,
            (val) -> Integer.parseInt(val));

Guava的函數Maps.transformValues是你正在尋找的,它與lambda表達式很好地配合:

Maps.transformValues(originalMap, val -> ...)

它絕對必須100%功能和流暢嗎? 如果沒有,那么這個怎么樣,就像它得到的一樣短:

Map<String, Integer> output = new HashMap<>();
input.forEach((k, v) -> output.put(k, Integer.valueOf(v));

如果你能忍受將流與副作用結合起來的羞恥感和內疚感

我的StreamEx庫增強了標准流API,提供了一個更適合轉換地圖的EntryStream類:

Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();

如果您不介意使用第三方庫,我的cyclops-react lib會擴展所有JDK Collection類型,包括Map 我們可以直接使用'map'運算符變換地圖(默認情況下,地圖會對地圖中的值進行處理)。

   MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                                .map(Integer::parseInt);

bimap可用於同時轉換鍵和值

  MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                               .bimap(this::newKey,Integer::parseInt);

總是存在學習目的的替代方法是通過Collector.of()來創建自定義收集器雖然toMap()JDK集電極這里是簡潔(+1 這里 )。

Map<String,Integer> newMap = givenMap.
                entrySet().
                stream().collect(Collector.of
               ( ()-> new HashMap<String,Integer>(),
                       (mutableMap,entryItem)-> mutableMap.put(entryItem.getKey(),Integer.parseInt(entryItem.getValue())),
                       (map1,map2)->{ map1.putAll(map2); return map1;}
               ));

聲明性和簡單的解決方案是:

yourMutableMap.replaceAll((key,val) - > return_value_of_bi_your_function); 鈮。 請注意修改地圖狀態。 所以這可能不是你想要的。

干杯: http//www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/

盡管可以如其他答案所示重新映射 stream 的collect部分中的鍵或/和值,但我認為它應該屬於map部分,因為 function 旨在轉換 stream 中的數據。接下來它應該很容易重復,而不會引入額外的復雜性。 SimpleEntry object 可以使用,自 Java 6 以來已經可用。

與 Java 8

import java.util.AbstractMap.SimpleEntry;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class App {

    public static void main(String[] args) {
        Map<String, String> x;
        Map<String, Integer> y = x.entrySet().stream()
                .map(entry -> new SimpleEntry<>(entry.getKey(), Integer.parseInt(entry.getValue())))
                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
    }

}

用 Java 9+

隨着 Java 9 的發布,引入了 Map 接口中的 static 方法,以便更輕松地創建一個條目,而無需實例化一個新的 SimpleEntry,如前一個示例所示。

import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class App {

    public static void main(String[] args) {
        Map<String, String> x;
        Map<String, Integer> y = x.entrySet().stream()
                .map(entry -> Map.entry((entry.getKey(), Integer.parseInt(entry.getValue())))
                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
    }

}

暫無
暫無

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

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