Java8:Stream / Map-Reduce / Collectorを使用してHashMap <X、Y>からHashMap <X、Z>へ


209

単純なJava ListY-> から「変換」する方法を知っていますZ。つまり、

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

今、私はマップと基本的に同じことをしたいと思います、すなわち:

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

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

ソリューションはString->に限定されるべきではありませんIntegerList上記の例のように、任意のメソッド(またはコンストラクター)を呼び出します。

回答:


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

リストのコードほど良くありません。新しい構築することはできませんMap.Entrysでmap()仕事が混入するので、呼び出しcollect()コール。


59
あなたは置き換えることができe -> e.getKey()Map.Entry::getKey。しかし、それは好み/プログラミングスタイルの問題です。
Holger

5
実際にはパフォーマンスの問題であり、ラムダの「スタイル」よりもわずかに優れているという提案
Jon Burgin '20

36

(+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))));
}

ここでいくつかのポイントを示します。1つ目は、ジェネリックでのワイルドカードの使用です。これにより、関数は多少柔軟になります。たとえば、出力マップに入力マップのキーのスーパークラスであるキーを含める場合は、ワイルドカードが必要になります。

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に制限されたワイルドカードを使用することは、エッジケースでのみ役立つことを認めます。)

2番目のポイントは、入力マップのを介してストリームを実行する代わりに、を介してストリームを実行entrySetしたことkeySetです。これにより、コードが少しクリーンになりますが、マップエントリからではなくマップから値をフェッチする必要があります。ちなみに、私は最初にkey -> key最初の引数として持っていましたがtoMap()、これは何らかの理由で型推論エラーで失敗しました。機能するように変更しまし(X key) -> keyFunction.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()ストリームの代わりに使用します。これは、マップで使用するのがやや不格好なコレクターが不要になるため、さらに単純になると思います。その理由はMap.forEach()、ストリームには値が1つしかないのに対し、キーと値は別々のパラメーターとして与えられるためです。その値としてキーとマップエントリのどちらを使用するかを選択する必要があります。マイナス面として、これには他のアプローチの豊かで流麗な良さが欠けています。:-)


11
Function.identity()かっこいいかもしれませんが、最初のソリューションではすべてのエントリに対してマップ/ハッシュルックアップが必要ですが、他のすべてのソリューションでは必要ないため、お勧めしません。
Holger

13

そのような一般的なソリューション

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));

ジェネリックを使用した素晴らしいアプローチ。私はそれは少し改善されると思います-私の答えを見てください。
スチュアートマークス

13

グアバの関数Maps.transformValuesはあなたが探しているものであり、ラムダ式でうまく機能します:

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

このアプローチは好きですが、java.util.Functionを渡さないように注意してください。Eclipseはcom.google.common.base.Functionを想定しているため、役に立たないエラーが発生します。FunctionはFunctionに適用できないため、「メソッドtransformValues(Map <K、V1>、Function <?super V1 、V2>)型のマップは引数(Map <Foo、Bar>、Function <Bar、Baz>)に適用できません "
mskfisher

を渡す必要があるjava.util.Function場合は、2つのオプションがあります。1.ラムダを使用してJavaの型推論でそれを理解できるようにして、問題を回避します。2. javaFunction :: applyのようなメソッド参照を使用して、型推論が理解できる新しいラムダを生成します。
Joe


5

標準のストリームAPIを強化するMy StreamExライブラリは、EntryStreamマップの変換により適したクラスを提供します。

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

4

常に目的を学ぶために存在する代替はtoMap()JDKコレクターがここに(+1簡潔ですがCollector.of()を介してカスタムコレクタを構築することですここ)。

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;}
               ));

私はこのカスタムコレクターをベースとして開始し、少なくともstream()の代わりにparallelStream()を使用する場合、binaryOperatorをより類似したものに書き換える必要があります。そうしないとmap2.entrySet().forEach(entry -> { if (map1.containsKey(entry.getKey())) { map1.get(entry.getKey()).merge(entry.getValue()); } else { map1.put(entry.getKey(),entry.getValue()); } }); return map1、値が減少すると失われます。
user691154

3

サードパーティのライブラリを使用しても構わない場合は、私のcyclops-react lib に、Mapを含むすべてのJDKコレクションタイプの拡張機能があります。'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);

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.