1前書き

このクイックチュートリアルでは、Java 8の機能を使って2つのマップをマージする方法を説明します。

より具体的には、重複するエントリを持つマップなど、さまざまなマージシナリオを検討します。


2初期化

はじめに、2つの

Map

インスタンスを定義しましょう。

private static Map<String, Employee> map1 = new HashMap<>();
private static Map<String, Employee> map2 = new HashMap<>();


Employee

クラスは次のようになります。

public class Employee {

    private Long id;
    private String name;

   //constructor, getters, setters
}

その後、

Map

インスタンスにデータをプッシュします。

Employee employee1 = new Employee(1L, "Henry");
map1.put(employee1.getName(), employee1);
Employee employee2 = new Employee(22L, "Annie");
map1.put(employee2.getName(), employee2);
Employee employee3 = new Employee(8L, "John");
map1.put(employee3.getName(), employee3);

Employee employee4 = new Employee(2L, "George");
map2.put(employee4.getName(), employee4);
Employee employee5 = new Employee(3L, "Henry");
map2.put(employee5.getName(), employee5);

後で使用するマップでは、

employee1

エントリと

employee5

エントリに同じキーがあります。


3

Map.merge()


  • Java 8は

    java.util.Map

    インターフェースに新しい

    merge()

    関数を追加しました** 。

これは、

merge()

関数がどのように機能するかを示したものです。指定されたキーがまだ値に関連付けられていない場合、または値がnullの場合は、キーを指定の値に関連付けます。

そうでなければ、値を与えられたリマッピング関数の結果で置き換えます。再マッピング関数の結果がNULLの場合は、結果が削除されます。

まず、

map1

からすべてのエントリをコピーして、新しい

HashMap

を作成しましょう。

Map<String, Employee> map3 = new HashMap<>(map1);

次に、マージ規則とともに

merge()

関数を紹介しましょう。

map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())

最後に、

map2

を繰り返し処理して、エントリを

map3

にマージします。

map2.forEach(
  (key, value) -> map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())));

プログラムを実行して

map3

の内容を印刷しましょう。

John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
George=Employee{id=2, name='George'}
Henry=Employee{id=1, name='Henry'}

その結果、

結合された

Map

は、前の

HashMap

エントリのすべての要素を持ちます。重複キーを持つエントリは1つのエントリにマージされました

また、最後のエントリの

Employee

オブジェクトには

map1



id

があり、値は

map2

から選択されています。

これは、マージ関数で定義した規則によるものです。

(v1, v2) -> new Employee(v1.getId(), v2.getName())


4

Stream.concat()


Java 8の

Stream

APIは、私たちの問題に対する簡単な解決策を提供することもできます。まず、


Map

インスタンスを1つの

Stream


に結合する必要があります。

Stream.concat()

操作の動作はまさにそれです。

Stream combined = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream());

ここではマップエントリセットをパラメータとして渡します。次に、結果を新しい

Map

** にまとめる必要があります。そのためには

Collectors.toMap()

を使用できます。

Map<String, Employee> result = combined.collect(
  Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

その結果、コレクターは既存のマップのキーと値を使用します。しかし、この解決策は完璧というわけではありません。私たちのコレクターが重複したキーを持つエントリを満たすとすぐに、それは

IllegalStateException

をスローします。

この問題を処理するために、コレクターに3番目の「合併」ラムダパラメーターを追加するだけです。

(value1, value2) -> new Employee(value2.getId(), value1.getName())

重複キーが検出されるたびにラムダ式を使用します。

最後に、まとめてみましょう。

Map<String, Employee> result = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
  .collect(Collectors.toMap(
    Map.Entry::getKey,
    Map.Entry::getValue,
    (value1, value2) -> new Employee(value2.getId(), value1.getName())));

最後に、コードを実行して結果を確認しましょう。

George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
Henry=Employee{id=3, name='Henry'}

見てのとおり、キー

“ Henry”

を持つ重複エントリは新しいキーと値のペアにマージされました。

新しい

Employee

のIDは

map2

から選択され、値は

map1


から選択されました。


5. Stream.of()


Stream

APIを引き続き使用するには、

Stream.of()

を使用して

Map

インスタンスを統合ストリームに変換できます。

ここでは、ストリームを扱うために追加のコレクションを作成する必要はありません。

Map<String, Employee> map3 = Stream.of(map1, map2)
  .flatMap(map -> map.entrySet().stream())
  .collect(Collectors.toMap(
    Map.Entry::getKey,
    Map.Entry::getValue,
    (v1, v2) -> new Employee(v1.getId(), v2.getName())));

まず、


map1



map2

を単一のストリームに変換します

。次に、ストリームをマップに変換します。ご覧のとおり、

toMap()

の最後の引数はマージ関数です。

v1

エントリからidフィールドを選び、

v2

から名前を選ぶことで、重複キーの問題を解決します。

プログラム実行後の印刷された

map3

インスタンス

George=Employee{id=2, name='George'}
John=Employee{id=8, name='John'}
Annie=Employee{id=22, name='Annie'}
Henry=Employee{id=1, name='Henry'}


6. 単純ストリーミング

さらに、

__stream()


pipelineを使ってマップエントリを組み立てることができます。以下のコードスニペットは、重複したエントリを無視して

map2



map1__のエントリを追加する方法を示しています。

Map<String, Employee> map3 = map2.entrySet()
  .stream()
  .collect(Collectors.toMap(
    Map.Entry::getKey,
    Map.Entry::getValue,
    (v1, v2) -> new Employee(v1.getId(), v2.getName()),
  () -> new HashMap<>(map1)));

予想通り、マージ後の結果は次のとおりです。

{John=Employee{id=8, name='John'},
Annie=Employee{id=22, name='Annie'},
George=Employee{id=2, name='George'},
Henry=Employee{id=1, name='Henry'}}


7.

StreamEx


JDKが提供するソリューションに加えて、人気のある

__StreamEx

__libraryも使用できます。

簡単に言うと、



StreamEx


は、

Stream

API

の拡張であり、その他にも多くの便利なメソッドが用意されています。キーと値のペアを操作するために


EntryStream

インスタンスを使用します

Map<String, Employee> map3 = EntryStream.of(map1)
  .append(EntryStream.of(map2))
  .toMap((e1, e2) -> e1);

アイデアは私たちの地図の流れを一つにまとめることです。それからエントリを新しい

map3

インスタンスに集めます。言及することが重要です、それは重複キーを扱うための規則を定義するのを助けるので

(e1、e2) – > e1

式。それがないと、コードは

IllegalStateException

をスローします。

そして今、結果:

{George=Employee{id=2, name='George'},
John=Employee{id=8, name='John'},
Annie=Employee{id=22, name='Annie'},
Henry=Employee{id=1, name='Henry'}}


8概要

この短い記事では、Java 8でマップをマージするさまざまな方法を学びました。具体的には、

Map.merge()、Stream API、StreamEx

libraryを使用しました。

いつものように、議論の間に使用されたコードはhttps://github.com/eugenp/tutorials/tree/master/java-collections-maps[GitHubについて]で見つけることができます。