JavaのMapを完全解説|3つの実装クラスの違いと現場での使い方

Mirai

JavaのMapってListと何が違うの?どんなときに使えばいいんだろう…?

Zetto

Mapはキーと値をセットで管理するデータ構造だね。「名前→スコア」のように関係性のあるデータを扱うときに、Listより圧倒的に使いやすくなるよ!

この記事では、JavaのMapの基本から、現場で役立つ実務パターンまで解説します。

Zettoのライタープロフィール

本記事の専門性
現役エンジニアのZettoです。Java GoldとJava Silverの資格を保有し、JavaでWebアプリ開発のフリーランス案件を複数経験しています。

Mapの使い方があいまいなままだと、現場でデータ管理の実装に詰まり、コードが無駄に複雑になりがちです。

この記事を読めば、JavaのMapの仕組み・3つの実装クラスの使い分け・現場で通用する書き方まで一通り理解できます。

ぜひ参考にしてみてください。

目次

JavaのMapとは何か

Javaのマップとは

JavaのMapは、キーと値をセットで管理するデータ構造です。

  • ListとMapの違いと使い分け
  • Mapが現場で活躍する場面

それぞれ詳しく解説します。

ListとMapの違いと使い分け

ListとMapは「データの取り出し方」が根本的に違います。

Listは番号(インデックス)でデータを管理します。Mapはキーを指定してデータを取り出します。

イメージとしては、こんな感じです。

  • List:「1番目・2番目・3番目」と番号で管理する棚
  • Map:「田中さん・山田さん」と名前で管理するロッカー

たとえば、ユーザーIDに対応するユーザー名を管理したい場合を考えます。Listだと「何番目か」でしか取り出せません。Mapなら「ユーザーID」を直接指定して取り出せます。

使い分けの基準はシンプルです。

  • 順番が大事で、番号でデータを管理したい → List
  • キーに紐づいたデータを素早く取り出したい → Map

JavaのListについては、以下の記事でArrayListの使い方を詳しく解説しています。MapとセットでJavaコレクションの全体像を理解したい方はあわせて読んでみてください。

Mapが現場で活躍する場面

Mapは「データの変換・集計・管理」の場面で特に活躍します。

キーを指定して値を瞬時に取り出せるのが最大の強みです。Listのように先頭から順番に探さなくてよいため、データ量が増えても処理が遅くなりにくいんですよね。

よく使われる場面を挙げます。

  • リクエストパラメータの管理(キー:パラメータ名、値:パラメータの中身)
  • マスタデータのキャッシュ(キー:コード値、値:名称)
  • データの集計(キー:カテゴリ名、値:件数)
  • JSONデータの一時保持(キー:フィールド名、値:データ)

僕がJava案件でよく使ったのは、マスタデータのキャッシュです。データベースから取得したコードと名称のペアをMapに入れておき、画面表示のたびに参照するパターンですね。

Zetto

MapはJavaの現場で毎日のように使うデータ構造です。ListやArrayと並んで「絶対に押さえておきたい3つ」に入る存在だと感じています。早めに使いこなせるようにしておくと、後々の学習がかなり楽になりますよ。

HashMap・LinkedHashMap・TreeMapの違いと選び方

Mapの実装の違い

Mapには代表的な実装クラスが3つあります。どれを使うかで動作が変わるため、違いを理解することが大切です。

  • 3つの実装クラスの特徴を比較する
  • どれを選ぶべきか?実務で使う判断軸
  • equals・hashCodeを実装しないと起きるバグ

ひとつずつ見ていきます。

3つの実装クラスの特徴を比較する

3つの実装クラスの特徴は以下の通りです。

  • HashMap:順序を保証しない。処理速度が最も速い
  • LinkedHashMap:追加した順序を保持する。HashMapより少し遅い
  • TreeMap:キーを昇順で自動ソートする。整列が必要なときに使う

コードで見るとわかりやすいですね。

// HashMap:出力順は不定(ランダム)
Map hashMap = new HashMap<>();
hashMap.put("banana", 3);
hashMap.put("apple", 1);
hashMap.put("cherry", 2);
System.out.println(hashMap); // {apple=1, cherry=2, banana=3} ← 順不同

// LinkedHashMap:追加した順番で出力される
Map linkedMap = new LinkedHashMap<>();
linkedMap.put("banana", 3);
linkedMap.put("apple", 1);
linkedMap.put("cherry", 2);
System.out.println(linkedMap); // {banana=3, apple=1, cherry=2} ← 追加順

// TreeMap:キーがアルファベット昇順に自動ソートされる
Map treeMap = new TreeMap<>();
treeMap.put("banana", 3);
treeMap.put("apple", 1);
treeMap.put("cherry", 2);
System.out.println(treeMap); // {apple=1, banana=3, cherry=2} ← 昇順

どのクラスもMap<K, V>インターフェース(インターフェース:クラスが持つべき機能の設計書)を実装しているため、基本的な操作は共通です。

どれを選ぶべきか?実務で使う判断軸

迷ったらまずHashMapを選ぶのが実務の基本です。

処理速度が最も速く、ほとんどのケースはHashMapで事足ります。順序や整列が必要なときだけ他のクラスを選べばよいです。

判断軸を整理するとこうなります。

  • 順序が不要・速度優先 → HashMap(デフォルトの選択肢)
  • 追加した順序を保持したい → LinkedHashMap(表示順の制御に使う)
  • キーをアルファベット順・数値順で並べたい → TreeMap

変数の型はMap<K, V>インターフェースで宣言するのがおすすめです。

// こう書く(インターフェース型で宣言)
Map map = new HashMap<>();

// こう書かない(実装クラス型で宣言するのは避ける)
HashMap map = new HashMap<>();
Zetto

インターフェース型で宣言しておくと、HashMapからLinkedHashMapへの切り替えが1行で済みます。現場でよく使われる書き方ですね。

Mapの基本操作とJava 8以降の便利な書き方

Mapの基本操作

Mapを実際に使いこなすために、基本操作からJava 8以降の書き方まで解説します。

  • put・get・removeなど基本メソッドの使い方
  • ループ処理の書き方|entrySet・keySet・forEachの違い
  • getOrDefault・computeIfAbsentなどJava 8の便利メソッド

さっそく見ていきましょう。

put・get・removeなど基本メソッドの使い方

まずMapの基本操作を押さえます。よく使うメソッドは以下の通りです。

  • put(key, value):キーと値を追加・上書きする
  • get(key):キーに対応する値を取得する
  • remove(key):キーと値を削除する
  • containsKey(key):キーが存在するか確認する(true/false)
  • size():格納されている件数を返す
Map scores = new HashMap<>();

// 追加
scores.put("田中", 85);
scores.put("山田", 72);
scores.put("佐藤", 91);

// 取得
System.out.println(scores.get("田中")); // 85

// 同じキーでputすると上書きされる
scores.put("田中", 90);
System.out.println(scores.get("田中")); // 90

// 削除
scores.remove("山田");

// キーの存在確認
System.out.println(scores.containsKey("山田")); // false

// 件数
System.out.println(scores.size()); // 2

get()で存在しないキーを指定するとnullが返ります。nullを考慮した書き方は、次の節で紹介するgetOrDefaultが便利です。

ループ処理の書き方|entrySet・keySet・forEachの違い

Mapの全データを処理したいとき、ループ処理を使います。書き方は3パターンあります。

Map scores = new HashMap<>();
scores.put("田中", 85);
scores.put("山田", 72);
scores.put("佐藤", 91);

// パターン1:entrySet(キーと値の両方が必要なとき)
for (Map.Entry entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

// パターン2:keySet(キーだけ必要なとき)
for (String key : scores.keySet()) {
    System.out.println(key);
}

// パターン3:forEach(Java 8以降・ラムダ式で簡潔に書ける)
scores.forEach((key, value) -> System.out.println(key + ":" + value));

実務ではforEachが最もよく使われます。ラムダ式(->の書き方)で短く書けて読みやすいためです。

ラムダ式が初めての場合は、以下の記事でJavaのラムダ式の基本を解説しています。forEachを含む活用例を詳しくまとめているので、あわせて確認してみてください。

Zetto

entrySetとkeySetの使い分けは「値も使うならentrySet・キーだけでいいならkeySet」です。迷ったらentrySetを使えば間違いないです。個人的には最近はほぼforEachしか書いていないですね。

getOrDefault・computeIfAbsentなどJava 8の便利メソッド

Java 8以降に追加されたメソッドを使うと、nullチェックのコードを大幅に減らせます。

getOrDefault(キーがなければデフォルト値を返す)

Map scores = new HashMap<>();
scores.put("田中", 85);

// キーが存在しないとき、nullの代わりに0を返す
int score = scores.getOrDefault("山田", 0);
System.out.println(score); // 0

get()でnullが返ってきてNullPointerException(ぬるぽ:nullに対して処理しようとして起きるエラー)が発生するケースを防げます。

computeIfAbsent(キーがなければ値を生成して追加する)

Map> groupMap = new HashMap<>();

// キー"A班"がなければ空のListを生成して追加し、そのまま要素をaddする
groupMap.computeIfAbsent("A班", k -> new ArrayList<>()).add("田中");
groupMap.computeIfAbsent("A班", k -> new ArrayList<>()).add("山田");

System.out.println(groupMap); // {A班=[田中, 山田]}

データをグループ分けして集計するときに特に便利な書き方です。

putIfAbsent(キーが存在しないときだけ追加する)

Map map = new HashMap<>();
map.put("田中", 85);

// 既に存在するので上書きしない
map.putIfAbsent("田中", 100);
// 存在しないので追加される
map.putIfAbsent("山田", 72);

System.out.println(map.get("田中")); // 85(変わらない)
System.out.println(map.get("山田")); // 72

Java 8以降のメソッドを活用すると、if文が減ってコードが読みやすくなります。

ここまででMapの基本操作と便利な書き方を一通り押さえられました。

Zetto

getOrDefaultは現場で本当によく使います。nullチェックのif文を書く前に「getOrDefaultで書けないか?」と考える癖をつけると、コードがすっきりします。computeIfAbsentはグループ集計のときに覚えると、一気に使えるシーンが広がりますよ。

現場で通用するMapの使い方|Spring Boot連携と実務パターン

実務的なMapの使い方

基本操作を理解したら、実際の現場での使い方を見ていきましょう。

  • Spring BootのControllerでMapを活用する
  • JSONとMapの相互変換(ObjectMapper)
  • デバッグ・ロギング時のMap出力の工夫

具体的に解説します。

Spring BootのControllerでMapを活用する

Spring Boot(スプリングブート:JavaでWebアプリを作る人気フレームワーク)のControllerでは、MapはAPIレスポンスの組み立てやパラメータ管理に登場します。

リクエストパラメータの受け取り

@GetMapping("/search")
public String search(@RequestParam Map params) {
    String keyword = params.getOrDefault("keyword", "");
    String category = params.getOrDefault("category", "all");
    // 検索処理...
    return "result";
}

@RequestParam Map<String, String>と書くと、URLのクエリパラメータをすべてMapで受け取れます。パラメータの数が可変のときに便利な書き方です。

レスポンスデータの組み立て

@GetMapping("/user/{id}")
@ResponseBody
public Map getUser(@PathVariable int id) {
    Map response = new LinkedHashMap<>();
    response.put("id", id);
    response.put("name", "田中太郎");
    response.put("email", "tanaka@example.com");
    return response; // Spring BootがJSON形式に自動変換する
}

戻り値をMapにすると、Spring BootがJSON形式に自動変換してくれます。レスポンスの構造をシンプルに表現できるのが利点です。

JSONとMapの相互変換(ObjectMapper)

実務ではJSONとMapを相互に変換する場面が多いです。Jackson(ジャクソン:JavaでJSONを扱う定番ライブラリ)のObjectMapperを使うのが一般的です。

JSONをMapに変換

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();
String json = "{\"name\":\"田中\",\"age\":30}";

Map map = mapper.readValue(json, Map.class);
System.out.println(map.get("name")); // 田中
System.out.println(map.get("age"));  // 30

MapをJSONに変換

Map map = new LinkedHashMap<>();
map.put("name", "田中");
map.put("age", 30);

String json = mapper.writeValueAsString(map);
System.out.println(json); // {"name":"田中","age":30}

APIから受け取ったJSONを一時的に処理するときや、動的にJSONを組み立てるときによく使うパターンです。

Zetto

僕の実務でも、APIレスポンスの内容を確認したりテストデータを作ったりするとき、この変換はよく書きます。専用のクラスを作るほどでもないケースに、Mapはちょうどいい選択肢ですね。

デバッグ・ロギング時のMap出力の工夫

Mapの中身を確認したいとき、そのままログに出力することができます。

Map scores = new HashMap<>();
scores.put("田中", 85);
scores.put("山田", 72);

// System.out.printlnでそのまま出力
System.out.println(scores); // {田中=85, 山田=72}

// ログフレームワーク(SLF4J)で出力
log.info("scores: {}", scores);

MapはtoString()が実装されているため、そのまま出力しても読める形式で表示されます。

複雑なMapをより見やすく出力したい場合は、forEachでループしながら出力する方法もあります。

scores.forEach((k, v) -> log.info("  {} -> {}", k, v));

バグを調査するとき、ログにMapの内容を出しておくだけで原因特定がかなり楽になります。ただし、本番環境のログに個人情報が入るMapを丸ごと出力するのは注意が必要です。出力する項目を絞るか、マスキング(一部を隠す処理)を入れるのが現場のお作法です。

ここまでで現場での実務パターンが理解できたと思います。

Zetto

Spring Bootとの組み合わせを知っておくと、MapがただのデータコンテナではなくAPIレスポンスや処理の橋渡し役として機能することがわかります。Javaエンジニアを目指すなら、この使い方は早めに押さえておくと実務がスムーズになりますよ。

java mapの演習問題

Mapの問題演習

理解を深めるために、演習問題を3つ用意しました。

  • 演習問題1:文字列の出現回数を数える
  • 演習問題2:MapのキーをアルファベットにTreeMapでソートする
  • 演習問題3:computeIfAbsentでグループ集計する

演習問題1:文字列の出現回数を数える

問題

以下の文字列リストを使って、各文字列が何回登場するかをMapで集計してください。

List words = Arrays.asList(
    "apple", "banana", "apple", "cherry", "banana", "apple"
);
// 期待する出力:{apple=3, banana=2, cherry=1}

解答例

Map countMap = new HashMap<>();
for (String word : words) {
    countMap.put(word, countMap.getOrDefault(word, 0) + 1);
}
System.out.println(countMap);

解説

getOrDefault(word, 0)で、キーが存在しない場合は0を返します。そこに1を足してput()で上書きすることで出現回数を加算しています。データ集計の場面で頻繁に使う定番パターンです。

演習問題2:MapのキーをアルファベットにTreeMapでソートする

問題

以下のMapを、キーのアルファベット順に並び替えて出力してください。

Map scores = new HashMap<>();
scores.put("Charlie", 70);
scores.put("Alice", 90);
scores.put("Bob", 80);
// 期待する出力:Alice=90, Bob=80, Charlie=70

解答例

Map sortedMap = new TreeMap<>(scores);
sortedMap.forEach((k, v) -> System.out.println(k + "=" + v));

解説

new TreeMap<>(元のMap)と書くと、既存のMapをTreeMapに変換できます。TreeMapは自動でキーを昇順ソートするため、並び替えが1行で完結します。

演習問題3:computeIfAbsentでグループ集計する

問題

以下のデータを使って、「班ごとのメンバーリスト」をMapで作成してください。

String[][] members = {
    {"田中", "A班"},
    {"山田", "B班"},
    {"佐藤", "A班"},
    {"鈴木", "B班"},
    {"高橋", "A班"}
};
// 期待する出力:{A班=[田中, 佐藤, 高橋], B班=[山田, 鈴木]}

解答例

Map> groupMap = new HashMap<>();
for (String[] member : members) {
    groupMap.computeIfAbsent(member[1], k -> new ArrayList<>()).add(member[0]);
}
System.out.println(groupMap);

解説

computeIfAbsent(key, k -> new ArrayList<>())は、キーが存在しなければ新しいArrayListを作って追加します。キーがすでに存在する場合はそのListをそのまま返します。グループ分け・集計処理でよく使う定番の書き方です。

Zetto

演習問題を手で書いてみると、「読んでわかった」と「自分で書ける」の差がよくわかります。

java mapを使いこなして現場で通用するエンジニアへ

Mapを使いこなすコツ

この記事では、JavaのMapについて基礎から実務パターンまで解説しました。

重要なポイントをまとめます。

  • Mapはキーとセットでデータをセットで管理するコレクション。ListやArrayとは用途が違う
  • 実装クラスは「迷ったらHashMap・順序保持ならLinkedHashMap・ソートが必要ならTreeMap」で選ぶ
  • 変数はMapインターフェース型で宣言する癖をつけると、切り替えが楽になる
  • Java 8のgetOrDefault・computeIfAbsentを使うとコードがすっきりする
  • 自作クラスをキーにするときはequals・hashCodeの実装を忘れない

MapはJavaの現場で毎日使うデータ構造です。最初はHashMapだけ覚えて、実際にコードを書きながらLinkedHashMap・TreeMapへと広げていくのが身につきやすい方法ですね。

演習問題を使って、ぜひ手を動かしてみてください。読んでわかった状態から、書いて動かせる状態まで持っていくのが大切です。

Mirai

Mapってこんなに使いどころがあるんだね!演習問題もやってみる!

Zetto

特にcomputeIfAbsentは実務で便利だから、手を動かして体に覚えさせてみてね!

Javaのfor文を使ったループ処理の基礎については、以下の記事で詳しく解説しています。Mapのループ処理をより深く理解したい方はあわせて確認してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次