1. 概要

このチュートリアルでは、JavaでIdentityHashMapクラスを使用する方法を学習します。 また、一般的なHashMapクラスとの違いについても調べます。 このクラスはMapインターフェースを実装していますが、Mapインターフェースのコントラクトに違反しています。

詳細なドキュメントについては、 IdentityHashMapjavaドキュメントページを参照してください。 一般的なHashMapクラスの詳細については、 JavaHashMapのガイドを参照してください。

2. IdentityHashMapクラスについて

このクラスは、Mapインターフェースを実装します。 Map インターフェースは、キー比較で equals()メソッドの使用を義務付けています。 ただし、IdentityHashMapクラスはそのコントラクトに違反しています。 代わりに、it は、キー検索操作で参照の同等性(==)を使用します。

検索操作中、 HashMaphashCode()メソッドを使用してハッシュしますが、 IdentityHashMapSystem.identityHashCode()メソッドを使用します。 また、検索操作にはハッシュテーブルの線形プローブ手法を使用します。

参照等式、 System.identityHashCode()、、および線形プローブ手法を使用すると、IdentityHashMapクラスのパフォーマンスが向上します。

3. IdentityHashMapクラスの使用

オブジェクトの構築とメソッドのシグネチャはHashMap、と同じですが、参照が等しいために動作が異なります。

3.1. IdentityHashMapオブジェクトの作成

デフォルトのコンストラクターを使用して作成できます。

IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>();

または、最初に予想される容量を使用して作成できます。

IdentityHashMap<Book, String> identityHashMap = new IdentityHashMap<>(10);

上記のように初期のexpectedCapcityパラメーターを指定しない場合、デフォルトの容量として21が使用されます。

別のマップオブジェクトを使用して作成することもできます。

IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>(otherMap);

この場合、作成されたIdentityHashMapotherMapのエントリで初期化します。

3.2. エントリの追加、取得、更新、削除

put()メソッドは、エントリを追加するために使用されます。

identityHashMap.put("title", "Harry Potter and the Goblet of Fire");
identityHashMap.put("author", "J. K. Rowling");
identityHashMap.put("language", "English");
identityHashMap.put("genre", "Fantasy");

putAll()メソッドを使用して、他のマップのすべてのエントリを追加することもできます。

identityHashMap.putAll(otherMap);

値を取得するには、 get()メソッドを使用します。

String value = identityHashMap.get(key);

キーの値を更新するには、 put()メソッドを使用します。

String oldTitle = identityHashMap.put("title", "Harry Potter and the Deathly Hallows");
assertEquals("Harry Potter and the Goblet of Fire", oldTitle);

上記のスニペットでは、 put()メソッドは更新後に古い値を返します。 2番目のステートメントは、oldTitleが以前の「タイトル」値と一致することを確認します。

remove()メソッドを使用して、要素を削除できます。

identityHashMap.remove("title");

3.3. すべてのエントリを反復処理します

entitySet()メソッドを使用して、すべてのエントリを反復処理できます。

Set<Map.Entry<String, String>> entries = identityHashMap.entrySet();
for (Map.Entry<String, String> entry: entries) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

keySet()メソッドを使用して、すべてのエントリを反復処理することもできます。

for (String key: identityHashMap.keySet()) {
    System.out.println(key + ": " + identityHashMap.get(key));
}

これらのイテレータは、fail-fastメカニズムを使用します。 反復中にマップが変更されると、ConcurrentModificationExceptionがスローされます。

3.4. その他の方法

他のMapオブジェクトと同様に機能するさまざまなメソッドも利用できます。

  • clear():すべてのエントリを削除します
  • containsKey():キーがマップに存在するかどうかを検索します。 参照のみが同等です
  • containsValue():値がマップに存在するかどうかを検索します。 参照のみが同等です
  • keySet():IDベースのキーセットを返します
  • size():エントリの数を返します
  • values():値のコレクションを返します

3.5. NullキーとNull値のサポート

IdentityHashMap は、キーと値の両方にnullを許可します。

IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>();
identityHashMap.put(null, "Null Key Accepted");
identityHashMap.put("Null Value Accepted", null);
assertEquals("Null Key Accepted", identityHashMap.get(null));
assertEquals(null, identityHashMap.get("Null Value Accepted"));

上記のスニペットは、キーと値の両方としてnullを保証します。

3.6. IdentityHashMapとの並行性

IdentityHashMapは、 HashMap と同じように、スレッドセーフではありません。 したがって、 IdentityHashMap エントリに並行してアクセス/変更する複数のスレッドがある場合は、それらを同期マップに変換する必要があります。

Collectionsクラスを使用して同期マップを取得できます。

Map<String, String> synchronizedMap = Collections.synchronizedMap(new IdentityHashMap<String, String>());

4. 参照等式の使用例

IdentityHashMap は、 equals()メソッドに対して参照等価(==)を使用して、キーオブジェクトを検索/保存/アクセスします。

次の4つのプロパティで作成されたIdentityHashMap

IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>();
identityHashMap.put("title", "Harry Potter and the Goblet of Fire");
identityHashMap.put("author", "J. K. Rowling");
identityHashMap.put("language", "English");
identityHashMap.put("genre", "Fantasy");

同じプロパティで作成された別のHashMap

HashMap<String, String> hashMap = new HashMap<>(identityHashMap);
hashMap.put(new String("genre"), "Drama");
assertEquals(4, hashMap.size());

新しい文字列オブジェクトgenre」をキーとして使用する場合、 HashMap はそれを既存のキーと同等にし、値を更新します。 したがって、ハッシュマップのサイズは4のままです。

次のコードスニペットは、IdentityHashMapの動作が異なることを示しています。

identityHashMap.put(new String("genre"), "Drama");
assertEquals(5, identityHashMap.size());

IdentityHashMap は、新しい「ジャンル」文字列オブジェクトを新しいキーと見なします。 したがって、サイズは5であると主張します。 「ジャンル」の2つの異なるオブジェクトが2つのキーとして使用され、値として DramaおよびFantasyが使用されます。

5. 可変キー

IdentityHashMapは、可変キーを許可します。 これは、このクラスのさらにもう1つの便利な機能です。

ここでは、単純なBookクラスを可変オブジェクトとして取り上げます。

class Book {
    String title;
    int year;
    
    // other methods including equals, hashCode and toString
}

最初に、Bookクラスの2つの可変オブジェクトが作成されます。

Book book1 = new Book("A Passage to India", 1924);
Book book2 = new Book("Invisible Man", 1953);

次のコードは、HashMapでの可変キーの使用法を示しています。

HashMap<Book, String> hashMap = new HashMap<>(10);
hashMap.put(book1, "A great work of fiction");
hashMap.put(book2, "won the US National Book Award");
book2.year = 1952;
assertEquals(null, hashMap.get(book2));

book2エントリはHashMapに存在しますが、その値を取得できませんでした。 これは変更されており、 equals()メソッドは変更されたオブジェクトと同等ではないためです。 これが、一般的なMapオブジェクトが不変オブジェクトをキーとして要求する理由です。

以下のスニペットは、IdentityHashMapと同じ可変キーを使用しています。

IdentityHashMap<Book, String> identityHashMap = new IdentityHashMap<>(10);
identityHashMap.put(book1, "A great work of fiction");
identityHashMap.put(book2, "won the US National Book Award");
book2.year = 1951;
assertEquals("won the US National Book Award", identityHashMap.get(book2));

興味深いことに、 IdentityHashMap は、キーオブジェクトが変更されている場合でも値を取得できます。 上記のコードでは、 assertEquals により、同じテキストが再度取得されます。 これは、参照が等しいために可能です。

6. いくつかのユースケース

その機能の結果として、IdeaYHashMapは他のMapオブジェクトとは一線を画しています。 ただし、一般的な目的では使用されないため、このクラスの使用には注意が必要です。

これは、次のような特定のフレームワークを構築するのに役立ちます。

  • 一連の可変オブジェクトのプロキシオブジェクトの保守
  • オブジェクト参照に基づいてクイックキャッシュを構築する
  • 参照を含むオブジェクトのメモリ内グラフを保持する

7. 結論

この記事では、 IdentityHashMap の操作方法、一般的な HashMap との違い、およびいくつかのユースケースについて学習しました。

完全なコードサンプルは、GitHubにあります。