HashMap の基本

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

HashMap の基本 とは

Java の HashMap を使って、キーと値のペアを保存し、商品名から価格を引き出すメソッドを作ろう。

HashMap を使ってキーと値を結びつける

ここまで List や配列で「順番に並んだデータ」を扱ってきました。今回は「名前から値を引く」タイプのデータ構造、Map を学びます。商品名から価格を、ユーザー ID からユーザー情報を、駅名から駅の路線情報を引く、といった用途は実務でも本当に多く、Map を一度覚えると一気にコードの幅が広がります。

Map の中でもまず最初に覚えるべきなのが HashMap です。Java 標準ライブラリの java.util.HashMap クラスで、内部的にはハッシュ表という仕組みを使ってデータを高速に出し入れできます。

配列や List が「番号で引く本棚」だとすれば、Map は「名前で引く辞書」です。apple という単語からその訳を引くのが辞書、apple という商品名から価格を引くのが Map、というイメージで覚えてください。

Map とは何か

Map<K, V> は、K (キー) と V (値) のペアを保持するデータ構造です。K には文字列や数値、V には任意の型を入れられます。

  • 商品名 (String) → 価格 (Integer)
  • ユーザー ID (Long) → ユーザー名 (String)
  • 県名 (String) → 都道府県人口 (Integer)

のように、「キーから値を一発で引く」のが Map の役目です。配列や List で同じことをしようとすると、ループで全件を回しながら if (item.equals("apple")) のような比較を毎回しなければなりません。データが 100 件なら 100 回、1 万件なら 1 万回。Map を使うと、件数に関係なくほぼ一瞬で値を取り出せます。

Map はインターフェースで、その実装クラスとして HashMap LinkedHashMap TreeMap などが用意されています。最初は使い分けを気にせず、HashMap だけ覚えれば十分です。

HashMap の宣言と基本操作

HashMap を使うには、まず import java.util.HashMap;import java.util.Map; を書きます。それから次のように宣言します。

Java

import java.util.HashMap; import java.util.Map; public class Demo { public static void main(String[] args) { Map<String, Integer> prices = new HashMap<>(); prices.put("apple", 100); prices.put("banana", 80); prices.put("cherry", 200); System.out.println(prices.get("apple")); // 100 System.out.println(prices.get("banana")); // 80 System.out.println(prices.containsKey("kiwi")); // false } }

Map<String, Integer> は「キーは String、値は Integer の Map」という意味です。左辺で型を書いたら、右辺は new HashMap<>() のように <> (ダイヤモンド演算子) で型を省略できます。

よく使うメソッドは以下のとおりです。

  • put(key, value) — キーと値のペアを登録する。同じキーで再度 put すると値が上書きされる
  • get(key) — キーに対応する値を取得する。存在しないキーなら null を返す
  • containsKey(key) — キーが登録されているかを boolean で返す
  • remove(key) — キーと値のペアを削除する
  • size() — 登録件数を返す

キーが存在しない時の落とし穴

HashMap で初心者がもっとも引っかかるのが、getnull を返すケースです。

Java

Map<String, Integer> prices = new HashMap<>(); prices.put("apple", 100); int price = prices.get("kiwi"); // NullPointerException!

get("kiwi")null を返しますが、それを int に代入しようとすると、null から int への変換 (unboxing) で NullPointerException が飛びます。Integer のままで受け取れば例外は出ませんが、その後の処理で null を考慮しないとやはり落ちます。

「Map から値を取り出した直後は、null チェックをするか getOrDefault を使う」と覚えてください。これだけで NPE の半分は防げます。

安全に取り出すための方法は次のとおりです。

Java

// パターン 1: containsKey で先に確認 if (prices.containsKey("kiwi")) { int price = prices.get("kiwi"); System.out.println(price); } else { System.out.println(-1); } // パターン 2: getOrDefault でデフォルト値を指定 int price = prices.getOrDefault("kiwi", -1); System.out.println(price); // -1

今回の課題でも、存在しないキーには -1 を返してほしいので、getOrDefault がスマートに書けます。コードを短くしたいときは積極的に使いましょう。

HashMap の中身を図で理解する

HashMap の内部では、キーを「ハッシュ関数」という計算機にかけて、配列の何番目に値を置くかを決めています。だから件数が増えても、ほぼ一定の時間で値が取り出せるわけです。

diagram (will load when visible)

apple をハッシュ関数にかけると 1 番のバケットに、banana は 2 番のバケットに、というように振り分けられます。get("apple") を呼ぶと、再度ハッシュ関数で 1 番を計算してそこから値を取り出すので、配列全体を順に見る必要がありません。

ハッシュ表は「バケット (小箱) がたくさん並んでいて、キーをハッシュ関数にかけて行き先のバケット番号を決める仕組み」と覚えるとイメージしやすくなります。詳しい仕組みはあとで深掘りすればよく、まずは「HashMap は速くてキー検索向き」とだけ理解しておけば十分です。

よくある間違い

HashMap を初めて触ったときに踏みがちな落とし穴を 3 つ紹介します。

  • null をキーに使ってしまうHashMap は技術的には null キーを 1 つだけ許容しますが、可読性が落ちるうえ ConcurrentHashMap では NPE を投げます。実務では null キーは使わないと決めておきましょう
  • auto-boxing を忘れて型エラーMap<String, Integer>intput するときは自動で Integer に箱詰め (auto-boxing) されますが、逆に int x = map.get("apple") のように受け取るとき、もし null が返ると NPE になります。Integer x = ... で受けるか getOrDefault でデフォルト値を指定するのが安全です
  • 順序が保たれていると勘違いするHashMapput した順番を保持しません。順番に処理したいときは LinkedHashMap を使うか、キーをリスト化してソートしてから回します

もうひとつ、地味だけど多いミスとして「put のキーと get のキーで微妙に違う文字列を使う」というのがあります。apple apple (先頭にスペース) は別物として扱われるので、ユーザー入力をキーにする場合は trim() をかけたり、小文字に揃えたりして「比較しやすい形」に整えてから Map に入れるのがおすすめです。

エラーが「キーが見つからない」系のときは、System.out.println(map.keySet()) で実際に入っているキーを全部書き出すと、ほぼ一発で原因が見つかります。

やってみよう

それでは実際の課題に挑戦してみましょう。

  1. Solution.priceOf(String item) メソッドを完成させる
  2. メソッド内で HashMap<String, Integer> を作り、apple100banana80cherry200put する
  3. 引数 item に対応する価格を返す。存在しないキーの場合は -1 を返す

getOrDefault を使えば 1 行でスマートに書けますが、もちろん containsKey + get の組み合わせでも構いません。両方書いてみて、好みのスタイルを身につけてください。

慣れてきたら、durian melon などキーを増やしてみたり、値を Double 型 (税込価格など) に変えてみたり、自由に書き換えて試しましょう。Map を自在に使えるようになると、Java で扱える問題の幅がぐっと広がります。

よくある質問

Q. HashMap と TreeMap はどう使い分けますか?

A. 順序を気にしないなら HashMap が高速(O(1))。キーを並べたい・範囲検索したいなら TreeMap(O(log n)、内部は赤黒木)。挿入順を保ちたい場合は LinkedHashMap が良い選択肢で、Cache 系の実装によく使われます。

Q. HashMap のキーに自作クラスを使う注意点は?

A. equals() と hashCode() を一緒にオーバーライドしてください。片方だけだとキーが正しくマッチしません。record(Java 16+)を使うと両方自動生成されるため、不変な値型なら record を選ぶとミスを防げます。

Q. 値の存在確認と取得を同時にやるには?

A. map.getOrDefault(key, defaultValue) や map.computeIfAbsent(key, k -> 初期値) が便利です。null チェックと put を 2 行で書くより簡潔で、原子的に動くため並行アクセスでも安全です。Streams と組み合わせる集計コードでよく登場します。

次のレッスン

次は Map を走査する で、Java の HashMap を使って、キーと値のペアを保存し、商品名から価格を引き出すメソッドを作ろう を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. HashMap の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. HashMap とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. クラス名は Solution、メソッド名は priceOf、引数は String item、戻り値の型は int
  2. メソッド内で HashMap<String, Integer> を作り、"apple"100"banana"80"cherry"200 の 3 件を put する
  3. 存在するキーならその価格を返し、存在しないキーには必ず -1 を返すこと

入出力例

test-cases.txt

priceOf("apple")100 priceOf("banana")80 priceOf("cherry")200 priceOf("kiwi")-1 priceOf("")-1

ヒント

main.java
main.java
学習モード

メモ

HashMap の基本

⌘S で保存