HashMap の基本
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 で初心者がもっとも引っかかるのが、get が null を返すケースです。
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 の内部では、キーを「ハッシュ関数」という計算機にかけて、配列の何番目に値を置くかを決めています。だから件数が増えても、ほぼ一定の時間で値が取り出せるわけです。
apple をハッシュ関数にかけると 1 番のバケットに、banana は 2 番のバケットに、というように振り分けられます。get("apple") を呼ぶと、再度ハッシュ関数で 1 番を計算してそこから値を取り出すので、配列全体を順に見る必要がありません。
ハッシュ表は「バケット (小箱) がたくさん並んでいて、キーをハッシュ関数にかけて行き先のバケット番号を決める仕組み」と覚えるとイメージしやすくなります。詳しい仕組みはあとで深掘りすればよく、まずは「
HashMapは速くてキー検索向き」とだけ理解しておけば十分です。
よくある間違い
HashMap を初めて触ったときに踏みがちな落とし穴を 3 つ紹介します。
nullをキーに使ってしまう —HashMapは技術的にはnullキーを 1 つだけ許容しますが、可読性が落ちるうえConcurrentHashMapでは NPE を投げます。実務ではnullキーは使わないと決めておきましょう- auto-boxing を忘れて型エラー —
Map<String, Integer>にintをputするときは自動でIntegerに箱詰め (auto-boxing) されますが、逆にint x = map.get("apple")のように受け取るとき、もしnullが返ると NPE になります。Integer x = ...で受けるかgetOrDefaultでデフォルト値を指定するのが安全です - 順序が保たれていると勘違いする —
HashMapはputした順番を保持しません。順番に処理したいときはLinkedHashMapを使うか、キーをリスト化してソートしてから回します
もうひとつ、地味だけど多いミスとして「put のキーと get のキーで微妙に違う文字列を使う」というのがあります。apple と apple (先頭にスペース) は別物として扱われるので、ユーザー入力をキーにする場合は trim() をかけたり、小文字に揃えたりして「比較しやすい形」に整えてから Map に入れるのがおすすめです。
エラーが「キーが見つからない」系のときは、
System.out.println(map.keySet())で実際に入っているキーを全部書き出すと、ほぼ一発で原因が見つかります。
やってみよう
それでは実際の課題に挑戦してみましょう。
Solution.priceOf(String item)メソッドを完成させる- メソッド内で
HashMap<String, Integer>を作り、apple→100、banana→80、cherry→200をputする - 引数
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 つができることを確認しましょう。
- HashMap の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. HashMap とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- クラス名は
Solution、メソッド名はpriceOf、引数はString item、戻り値の型はint - メソッド内で
HashMap<String, Integer>を作り、"apple"→100、"banana"→80、"cherry"→200の 3 件をputする - 存在するキーならその価格を返し、存在しないキーには必ず
-1を返すこと
入出力例
test-cases.txt
priceOf("apple") → 100
priceOf("banana") → 80
priceOf("cherry") → 200
priceOf("kiwi") → -1
priceOf("") → -1