Java Tips | コレクション:デフォルトMap取得

Java Java
スポンサーリンク

「デフォルトMap取得」は「なかったとき、何を返すか」を先に決めておく技

Map を使っていると、ほぼ必ずこういうコードが出てきます。

Integer count = map.get(key);
if (count == null) {
    count = 0;
}
Java

「キーがなかったら 0 にしたい」「null を扱いたくない」。
この「なかったとき、どうするか」を毎回 if で書くのはダルいし、バグの元にもなります。

そこで使うのが「デフォルトMap取得」です。
「キーがなかったら、この値(あるいはこのロジック)を使う」と、あらかじめ決めておく考え方です。


基本形1:getOrDefault で「なかったら固定値」を返す

if-null パターンを一行にまとめる

Java 8 以降、Map#getOrDefault が使えます。

import java.util.HashMap;
import java.util.Map;

public class GetOrDefaultBasic {

    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("山田", 80);

        int yamada = scores.getOrDefault("山田", 0);
        int sato   = scores.getOrDefault("佐藤", 0);

        System.out.println(yamada); // 80
        System.out.println(sato);   // 0(キーがないのでデフォルト)
    }
}
Java

ここでの重要ポイントは二つです。

一つ目は、「キーが存在しないときに返す“固定値”を第二引数で指定できる」ことです。
getOrDefault("佐藤", 0) は、「佐藤がいなければ 0」と一行で書けています。

二つ目は、「キーが存在しても、その値が null の場合は null が返る」という点です。
getOrDefault は“キーがないとき”だけデフォルトを使います。
「値が null のときもデフォルトにしたい」なら、別途工夫が必要です(後述)。


基本形2:computeIfAbsent で「なかったら作って入れて返す」

「デフォルト値を返す」と同時に「Map にも登録したい」場合

集計やカウントでよくあるのが、こういうパターンです。

Integer count = map.get(key);
if (count == null) {
    count = 0;
}
map.put(key, count + 1);
Java

これを computeIfAbsent でスッキリ書けます。

import java.util.HashMap;
import java.util.Map;

public class ComputeIfAbsentBasic {

    public static void main(String[] args) {
        Map<String, Integer> counts = new HashMap<>();

        String key = "apple";

        int newCount = counts.computeIfAbsent(key, k -> 0) + 1;
        counts.put(key, newCount);

        System.out.println(counts.get("apple")); // 1
    }
}
Java

もう少し実用的に書くなら、こうです。

int increment(String key, Map<String, Integer> counts) {
    int current = counts.getOrDefault(key, 0);
    int next = current + 1;
    counts.put(key, next);
    return next;
}
Java

あるいは、merge を使うとさらに一行で書けますが、ここでは「デフォルト取得」の話に絞ります。

computeIfAbsent の本質は、「キーがなかったら“計算して作った値”を Map に入れて、その値を返す」です。

V value = map.computeIfAbsent(key, k -> createDefaultValue(k));
Java

ここでの重要ポイントは三つです。

一つ目は、「デフォルト値を“その場で計算できる”」ことです。
k -> new ArrayList<>() のように、キーに応じて新しいオブジェクトを作れます。

二つ目は、「作ったデフォルト値を Map にも登録してくれる」ことです。
次回同じキーで呼んだときは、もう一度作らずに既存の値を返してくれます。

三つ目は、「“なかったら作る”というパターンを一箇所に閉じ込められる」ことです。
これにより、呼び出し側は「とにかくこのキーの値が欲しい。なければ作ってくれればいい」という意図だけを書けます。


よくある実務パターン:Map<K, List<V>> で「なかったら空リストを作る」

グルーピングや集約で頻出する「リストのデフォルト取得」

例えば、「カテゴリ → 商品一覧」のような Map を手で組み立てるとき、
こんなコードを書きがちです。

Map<String, List<Product>> map = new HashMap<>();

for (Product p : products) {
    List<Product> list = map.get(p.getCategory());
    if (list == null) {
        list = new ArrayList<>();
        map.put(p.getCategory(), list);
    }
    list.add(p);
}
Java

これを computeIfAbsent を使って整理すると、こうなります。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GroupingSample {

    public static void main(String[] args) {
        Map<String, List<String>> categoryToItems = new HashMap<>();

        add(categoryToItems, "果物", "りんご");
        add(categoryToItems, "果物", "みかん");
        add(categoryToItems, "野菜", "にんじん");

        System.out.println(categoryToItems);
        // {果物=[りんご, みかん], 野菜=[にんじん]}
    }

    private static void add(Map<String, List<String>> map,
                            String category,
                            String item) {
        List<String> list =
                map.computeIfAbsent(category, k -> new ArrayList<>());
        list.add(item);
    }
}
Java

ここで深掘りしたい重要ポイントは三つです。

一つ目は、「computeIfAbsent(category, k -> new ArrayList<>()) が、“なかったら空のリストを作って入れる”を一行で表現している」ことです。

二つ目は、「呼び出し側は“とにかくリストを取って add する”だけに集中できる」ことです。
add メソッドの中身は、「リストを取得(なければ作る)→ 要素を追加」という自然な流れになっています。

三つ目は、「“デフォルト値の生成ロジック”をラムダとして渡せるので、テストや差し替えがしやすい」ことです。
k -> new ArrayList<>() の部分を別メソッドに切り出すこともできます。


「値が null のときもデフォルトにしたい」場合

getOrDefault だけでは足りないケース

getOrDefault は「キーが存在しないとき」にだけデフォルトを返します。
しかし、Map に「キーはあるが値が null」というケースもありえます。

Map<String, Integer> map = new HashMap<>();
map.put("A", null);

int v = map.getOrDefault("A", 0); // v は null(オートアンボクシングで NPE の危険)
Java

「キーがあっても値が null ならデフォルトにしたい」なら、ユーティリティを一枚かませると安全です。

public final class DefaultMaps {

    private DefaultMaps() {}

    public static <K, V> V getOr(
            Map<K, V> map,
            K key,
            V defaultValue
    ) {
        if (map == null) {
            return defaultValue;
        }
        V value = map.get(key);
        return value != null ? value : defaultValue;
    }
}
Java

使い方はこうです。

Integer v = DefaultMaps.getOr(map, "A", 0);
Java

ここでの重要ポイントは、
「“キーがない”と“値が null”の両方を、同じようにデフォルト扱いにしている」ことです。

業務的に「null は意味のある値なのか」「単なる未設定なのか」を決めたうえで、
このようなユーティリティを使うかどうか判断するとよいです。


まとめ:デフォルトMap取得ユーティリティで身につけてほしい感覚

デフォルトMap取得は、
単に「便利メソッドを知る」話ではなく、
「“なかったときどうするか”を設計として先に決めて、コードに刻む技術」です。

キーがないときに固定値を返したいなら getOrDefault
キーがないときに“作って入れて返す”なら computeIfAbsent
Map<K, List<V>> のような構造では、「なかったら空リストを作る」パターンをユーティリティ化する。
「値が null のときもデフォルト扱いにするかどうか」を、プロジェクトとして決めておく。

あなたのコードのどこかに、
毎回 if (map.get(key) == null) { ... } と書いている箇所があれば、
それを一度「デフォルトMap取得ユーティリティ」に置き換えられないか眺めてみてください。

その小さな整理が、
「null と“なさ”を、落ち着いてコントロールできるエンジニア」への、
確かな一歩になります。

タイトルとURLをコピーしました