HOWTO · Java

Java で HashMap をキーで並べ替える

この記事では、Java で HashMap をキーでソートする方法を紹介します。

このページの内容

Java 言語の HashMap という用語は、Map インターフェースから派生したコレクションです。このコレクションまたはクラスは java.util パッケージに含まれており、データをキーと値のペアで格納します。マップ上に 2つのキーを含めることはできないことに注意してください。重複するキーが挿入されると、それぞれのキーの値が新しい値に置き換えられます。

Java の TreeMap クラスを使用してキーセットを並べ替える

以下は、HashMap をキーでソートする方法を示すコードブロックです。

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class HashMapSortByKey {
  public static void main(String[] args) {
    Map<String, String> map = new HashMap<>();
    map.put("2", "Value5");
    map.put("3", "Value1");
    map.put("1", "Value2");
    map.put("4", "Value3");
    map.put("9", "Value4");
    map.put("hi11", "Value6");
    map.put("hi0", "Value7");
    System.out.print("Unordered List: ");
    for (String s : map.keySet()) {
      System.out.print(" " + s);
    }
    System.out.println();
    System.out.print("Ordered List: ");
    TreeMap<String, String> map1 = new TreeMap<>(map);
    for (String s : map1.keySet()) {
      System.out.print(" " + s);
    }
  }
}

上記のコードブロックでは、map オブジェクトは、new キーワードを指定した HashMap のデフォルトコンストラクターを使用して宣言されています。このステートメントは、HashSet クラスのインスタンスを作成し、その値を Map インターフェイス参照に割り当てます。HashMap クラスは Map インターフェースを内部的に実装するため、タイプは互換性があります。

マップは、いくつかのキー値のセットで初期化されます。挿入は、キーを指定された値にバインドする put メソッドを使用して行われます。キーがマップにすでに存在する場合、メソッドは previous value を返し、前の値を指定された値でオーバーライドします。キーがマップにまだ存在しない場合、関数は null 値を返します。キーまたは値のプロパティで値の挿入が禁止されている場合は、IllegalArgumentException がスローされます。

マップを反復処理するために、for-each ループが使用されます。map.keySet 関数は、マップ内のすべてのキーの Set 形式を返します。キーセット関数は、変数が反復できる for-each ループでコレクションを取得します。したがって、キーを出力します。値は順序付けられていない形式で出力され、ユーザーが出力を取得する明確な方法はありません。

マップキーをソートするために、TreeSet クラスが使用されます。map 変数は、インスタンス化時に TreeSet コンストラクターのコンストラクターパラメーターとして指定されます。このクラスは、キーをソートするための Comparable インターフェースを実装しています。結果のマップ map1 が出力されると、ソートされたキーが出力に出力されます。

以下は、上記のコードブロックの出力です。

出力:

Unordered List:  1 hi11 2 3 4 9 hi0
Ordered List:  1 2 3 4 9 hi0 hi11

Java 8 関数を使用したキーセットのソート

Java 8 は、関数型プログラミングの特権を提供します。これは、チェーン関数の操作に役立ちます。以下のプログラムでは、インスタンス化と初期化がステートメントで行われていることがわかります。一方、最初のサンプルコードでは、マップにデータを入力するのは面倒な作業です。単一のステートメントでマップをインスタンス化するための Stream と関数の理解を以下に示します。

Streamsjava.util パッケージのインターフェースであり、操作のシーケンスを一度に処理する柔軟性を提供します。Streams は、エミッターがデータを送信するパイプラインで機能します。ユーザーのニーズに応じて、フィルタリング、処理、変換などが行われます。

import static java.util.AbstractMap.SimpleEntry;
import static java.util.stream.Collectors.toMap;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Stream;

public class HashMapSortByKey {
  public static void main(String[] args) {
    Map<String, String> map =
        Stream
            .of(new SimpleEntry<>("key6", "value1"), new SimpleEntry<>("key12", "value2"),
                new SimpleEntry<>("key9", "value3"))
            .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue));

    System.out.print("Unordered List: ");
    for (String s : map.keySet()) {
      System.out.print(" " + s);
    }
    Map<String, String> mapSortedByKey =
        map.entrySet()
            .stream()
            .sorted(Map.Entry.<String, String>comparingByKey().reversed())
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                (oldVal, newValue) -> oldValue, LinkedHashMap::new));

    System.out.println();
    System.out.print("Ordered List: ");
    for (String s1 : mapSortedByKey.keySet()) {
      System.out.print(" " + s1);
    }
  }
}

Java のマップ初期化ストリーム関数を理解する

まず、Stream は、指定されたストリームを順番に返す of() 関数を呼び出します。この関数は、SimpleEntry クラスのオブジェクトを作成して、Key-Value フォームのマッピングを作成します。このクラスは Entry インターフェースを実装し、マップのコレクションビューを返す entrySet メソッドを持っています。

Key-Value エントリが形成されると、collect 関数を使用して値がマップに収集されます。これはターミナル関数であり、collect 関数の後にメソッドを呼び出すことはできません。この関数は、ストリーム値を目的のデータ構造にパッケージ化またはバインドします。

パラメータとして指定された入力は、常に Collector 参照です。toMap は、マップ内のストリーム要素をバインドする Collector を返す Collectors クラスの静的関数です。この関数は、キーマッパーとキー値関数をパラメーターとして受け取ります。ここで、メソッド参照::演算子は、指定されたクラスの関数を呼び出すことを意味します。

キーマッパーでは、getKey 関数が入力された EntrySet ストリームからキーを抽出します。同様に、Key Value 関数では、getValue 関数が呼び出されて、EntrySet ストリームから値を取得します。

上記のコードの Collector``Function 変数は、機能のための単一の抽象メソッドを持つ Java の機能インターフェイスです。抽象メソッドの定義は、それらを実装するクラスで定義されます。

Java のマップ変換ストリーム関数を理解する

形成されたばかりの map インスタンス上で、entrySet 関数が呼び出されます。この関数は、マップエントリのセットビューを返し、マップインターフェイスに存在します。これらのエントリに対して、stream メソッドが呼び出され、シーケンシャルストリームのエントリが変換されます。エントリのストリームを介して、sorted 関数が呼び出されます。

Stream 要素を比較するには、コンパレータが必要です。ソートされた関数は、最終的に、指定されたエントリのソートされたストリームを返します。comparingByKey 関数は、キーのデフォルトの順序で Map.Entry コンパレータを返します。コンパレータはキーセットを返します。見つかったキーが null の場合、関数から NullPointerException がスローされます。

エントリセットが取得されると、コレクション内の要素の順序を逆にするために reversed 関数が呼び出されます。最後に、collect 関数がマップストリームを介して呼び出されます。この関数は端末演算子であるため、collect 関数の後に操作を呼び出すことはできません。関数内で、toMap 関数が呼び出され、ストリームがマップに変換されます。

この関数は 4つのパラメーターを取ります。keyMapper はキーを生成する関数、valueMapper はその値を作成するマッピング関数、mergeFunction は関数をマージする BinaryOperatormapSupplier は結果が挿入される新しい空のマップを返す Supplier 関数。最後に、結果のコンソール出力が上に出力されます。

出力:

Unordered List:  key12 key6 key9
Ordered List:  key9 key6 key12