Java 8でラムダを使用してMap <K、V>を別のMap <K、V>に変換するにはどうすればよいですか?


140

私はJava 8を検討し始めたばかりで、ラムダを試してみるために、最近書​​いた非常に単純なものを書き直そうと思った。文字列から列へのマップを別の文字列から列へのマップに変換する必要があります。新しいマップの列は、最初のマップの列の防御コピーです。列にはコピーコンストラクタがあります。これまでのところ最も近いのは:

    Map<String, Column> newColumnMap= new HashMap<>();
    originalColumnMap.entrySet().stream().forEach(x -> newColumnMap.put(x.getKey(), new Column(x.getValue())));

しかし、もっと良い方法があるはずだと私は確信しており、アドバイスをいただければ幸いです。

回答:


222

あなたはコレクターを使うことができます:

import java.util.*;
import java.util.stream.Collectors;

public class Defensive {

  public static void main(String[] args) {
    Map<String, Column> original = new HashMap<>();
    original.put("foo", new Column());
    original.put("bar", new Column());

    Map<String, Column> copy = original.entrySet()
        .stream()
        .collect(Collectors.toMap(Map.Entry::getKey,
                                  e -> new Column(e.getValue())));

    System.out.println(original);
    System.out.println(copy);
  }

  static class Column {
    public Column() {}
    public Column(Column c) {}
  }
}

6
上記で注意すべき重要な(そして直観に反する)重要なことは、変換はmap()ストリーム操作ではなくコレクターで行われることです
Brian Agnew

24
Map<String, Integer> map = new HashMap<>();
map.put("test1", 1);
map.put("test2", 2);

Map<String, Integer> map2 = new HashMap<>();
map.forEach(map2::put);

System.out.println("map: " + map);
System.out.println("map2: " + map2);
// Output:
// map:  {test2=2, test1=1}
// map2: {test2=2, test1=1}

このforEachメソッドを使用して、必要な操作を実行できます。

あなたがそこでしていることは:

map.forEach(new BiConsumer<String, Integer>() {
    @Override
    public void accept(String s, Integer integer) {
        map2.put(s, integer);     
    }
});

ラムダに簡略化できるもの:

map.forEach((s, integer) ->  map2.put(s, integer));

また、既存のメソッドを呼び出すだけなので、メソッドリファレンスを使用できます。

map.forEach(map2::put);

8
ここで舞台裏で何が起こっているのかを正確に説明したのが好きです。しかし、これで値が防御的なコピーに変わった新しいマップではなく、元のマップのストレートコピーが得られると思います。(map2 :: put)を使用できる理由は、putメソッドに入力されるのと同じ引数が(s、integer)のようにラムダに入力されるためです。防御的なコピーを行うには、それをする必要がoriginalColumnMap.forEach((string, column) -> newColumnMap.put(string, new Column(column)));あるのでしょうか、それとも短縮できますか?
annesadleir 2014年

はい、そうです。同じ引数を渡すだけの場合はメソッド参照を使用できますが、この場合new Column(column)はをパラメーターとして持っているので、それを使用する必要があります。
Arrem、2014年

セッションの更新など、両方のマップがすでに提供されている場合に機能するため、この回答の方が好きです。
David Stanley、

そのような答えのポイントを理解するのかわからない。このように、既存のMapからほとんどすべてのMap実装のコンストラクターを書き換えています。
キネオリアン

13

すべてのエントリを新しいマップに再挿入しない方法は、HashMap.clone内部で再ハッシュも実行するため、最速ではないはずです。

Map<String, Column> newColumnMap = originalColumnMap.clone();
newColumnMap.replaceAll((s, c) -> new Column(c));

これは非常に読みやすい方法であり、非常に簡潔です。HashMap.clone()と同じように見えますMap<String,Column> newColumnMap = new HashMap<>(originalColumnMap);。それが裏で違うかどうかはわかりません。
annesadleir 2014年

2
このアプローチには、Map実装の動作を維持するという利点があります。つまり、MapEnumMapまたはのSortedMap場合、結果Mapも同様です。SortedMap特別なComparatorものの場合、それは大きな意味上の違いをもたらすかもしれません。まあ、同じことがIdentityHashMap…にも当てはまります
Holger

8

シンプルに保ち、Java 8を使用してください:

 Map<String, AccountGroupMappingModel> mapAccountGroup=CustomerDAO.getAccountGroupMapping();
 Map<String, AccountGroupMappingModel> mapH2ToBydAccountGroups = 
              mapAccountGroup.entrySet().stream()
                         .collect(Collectors.toMap(e->e.getValue().getH2AccountGroup(),
                                                   e ->e.getValue())
                                  );

この場合:map.forEach(map2 :: put); シンプルな:)です
SES

5

プロジェクトでGuava(v11以上)を使用する場合は、Maps :: transformValuesを使用できます。

Map<String, Column> newColumnMap = Maps.transformValues(
  originalColumnMap,
  Column::new // equivalent to: x -> new Column(x) 
)

注:このマップの値は遅延評価されます。変換にコストがかかる場合は、グアバのドキュメントで提案されているように、結果を新しいマップにコピーできます。

To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.

注:ドキュメントによると、これはoriginalColumnMapの遅延評価ビューを返すため、エントリがアクセスされるたびに関数Column :: newが再評価されます(マッピング関数が高価な場合は望ましくない場合があります)
Erric

正しい。変換にコストがかかる場合は、ドキュメントで提案されているように、それを新しいマップにコピーするオーバーヘッドで問題ないでしょう。To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.
Andrea Bergonzo

確かに、しかし、ドキュメントを読み飛ばす傾向がある人のために、答えの脚注として追加する価値があるかもしれません。
Erric

@Erricわかりました、追加されました。
Andrea Bergonzo

3

ある種の変換を行う必要がある場合に備えて、キーと値に同時にアクセスできる別の方法を次に示します。

Map<String, Integer> pointsByName = new HashMap<>();
Map<String, Integer> maxPointsByName = new HashMap<>();

Map<String, Double> gradesByName = pointsByName.entrySet().stream()
        .map(entry -> new AbstractMap.SimpleImmutableEntry<>(
                entry.getKey(), ((double) entry.getValue() /
                        maxPointsByName.get(entry.getKey())) * 100d))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

1

サードパーティのライブラリを使用してもかまわない場合は、私のcyclops-react lib に、Mapを含むすべてのJDKコレクションタイプの拡張機能があります。mapまたはbimapメソッドを直接使用して、マップを変換できます。MapXは、既存のMapから構築できます。

  MapX<String, Column> y = MapX.fromMap(orgColumnMap)
                               .map(c->new Column(c.getValue());

キーも変更したい場合は、次のように書くことができます

  MapX<String, Column> y = MapX.fromMap(orgColumnMap)
                               .bimap(this::newKey,c->new Column(c.getValue());

bimapを使用すると、キーと値を同時に変換できます。

MapXがMapを拡張するため、生成されたマップは次のように定義することもできます。

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