はじめに
こちらは、自分用にまとめたHashMapに関する備忘録です。
個人的なメモとして書いているため、内容に誤りがあるかもしれません。その点を理解した上で参考にしていただけると嬉しいです。
何かあればご意見お寄せいただけると幸いです。
HashMapとは
まず、キーと値の組み合わせで構成されるMapインターフェースがあります。
HashMapはそれを実装した具象クラスで、Java標準のjava.util パッケージに含まれています。
名前の通りハッシュ値を利用することで高速なデータの検索や取得が可能です。
特徴は以下の通り。
- 重複したキーは持てません
- 順番は持ちません
- nullキーは一つまで・null値は複数格納可能です
HashMap利用時の注意点
- キーのハッシュ値の衝突(重複)が発生すると検索効率が低下します
- ハッシュ表のサイズはあらかじめ決まっています
- 頻繁なリサイズはパフォーマンス低下の原因になるため、次々に要素を追加していく処理には注意すべきです
ちょっと便利なメソッド
containsValue
指定した値がHashMapに含まれているかを判定します。
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
System.out.println(map.containsValue("value1")); // true
computeIfPresent/computeIfAbsent
指定されたキーが存在する/しない場合にのみ処理を行います。
Map<String, Integer> map = new HashMap<>();
map.put("key1", 1);
map.computeIfPresent("key1", (key, value) -> value + 1);
System.out.println(map.get("key1")); // 出力: 2
putIfAbsent
通常のput
はすでに存在するキーに対して値を上書きしますが、putIfAbsent
を使うと、キーが存在しない場合のみ値を追加できます。
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.putIfAbsent("key1", "value2");
System.out.println(map.get("key1")); // value1(上書きされない)
値の計算が発生&戻り値を使用したい場合はcomputeIfAbsent
が適していますが、こちらの方が読みやすいと思っています。
merge
キーが存在しない場合は第2引数をセットし、存在する場合は指定のルールで値を更新します。put
とputIfAbsent
の複合のようなものですね。以前の値をベースに更新していく処理にいいと思います。
if文で条件分岐が必要ないのが助かります。
Map<String, Integer> map = new HashMap<>();
map.put("key1", 1);
map.merge("key1", 5, (v1, v2) -> v1 + v2);
System.out.println(map); // {key1=6}
getOrDefault
キーが存在しない場合にデフォルト値を返します。いちいちcontainsKeyに聞きに行かなくて済みます。
Map<String, String> map = new HashMap<>();
System.out.println(map.getOrDefault("key1", "default")); // default
おまけ サイズの宣言
例えばループで大量のデータを追加する場合、リサイズが何度も発生してしまいます。
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 100000; i++) {
map.put(i, "value" + i); // 途中で何度もリサイズが発生
}
new HashMap<>(100000)
のように予めサイズを宣言すれば不要なリサイズを防げます。
ハマりがちな落とし穴
forEachはHashMapがnullのときにこける
Optionalを使うことで回避できます。
Optional.ofNullable(map).ifPresent(m -> m.forEach((key, value) ->
System.out.println(key + ": " + value)
));
equalsの挙動
これはHashMapに限らずですが、equals
比較が思うように動作しないことがあるため注意が必要です。Arrayを使用するときが代表例です。
Map<String, String[]> map1 = new HashMap<>();
map1.put("key1", new String[]{"value1", "value2"});
Map<String, String[]> map2 = new HashMap<>();
map2.put("key1", new String[]{"value1", "value2"});
System.out.println(map1.equals(map2)); // false
この例では、配列の参照が異なるためequals
比較が失敗します。Arrays.equals
を使用するか、ListやSetなどほかのクラスを使用しましょう。
System.out.println(Arrays.equals(map1.get("key1"), map2.get("key1"))); // true
補足
HashMap以外にどのようなMapがあるの?というとjava.utilにはLinkedHashMap, TreeMapなどがあります。
- LinkedHashMap:キーを挿入もしくはアクセス順で保存してくれます
- TreeMap:キーを(指定の規則にしたがって)自動的にソートしてくれます
自分は使用したことがありませんが、順序が重要な場合は別クラスの使用を検討したほうが良さそうです。
最後に
HashMapは自分にとって直感的に使用でき便利ですが、改めて仕様を確認したことにより、リサイズのコストなど注意すべき点もあることに気付きました。
普段何気なく使っているクラスでも、きちんと理解して、効率的に活用できるようになりたいですね~