Java Tips | コレクション:安全get

Java Java
スポンサーリンク

「安全get」は「落ちてもおかしくない場所に、クッションを敷いておく」技

List#getMap#get は、とてもよく使う基本メソッドですが、
そのまま使うと、意外と簡単に例外や NullPointerException を起こします。

インデックスが範囲外だった。
キーが存在しなかった。
そもそもコレクション自体が null だった。

「落ちてもおかしくない場所」に、そのまま生身で突っ込むのではなく、
あらかじめ“クッション(安全get)”を敷いておく——それが今回のテーマです。


List 向けの安全get

範囲外アクセスで落ちないようにする

まずは List から安全に要素を取り出すユーティリティです。

import java.util.List;

public final class SafeGets {

    private SafeGets() {}

    public static <T> T getOrNull(List<T> list, int index) {
        if (list == null) {
            return null;
        }
        if (index < 0 || index >= list.size()) {
            return null;
        }
        return list.get(index);
    }
}
Java

使い方はこうです。

List<String> names = List.of("山田", "佐藤", "鈴木");

String a = SafeGets.getOrNull(names, 1);  // "佐藤"
String b = SafeGets.getOrNull(names, 10); // null(安全に失敗)
String c = SafeGets.getOrNull(null, 0);   // null
Java

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

一つ目は、「インデックスが範囲外でも IndexOutOfBoundsException を投げずに null を返す」ことです。
「とりあえずあれば使う、なければ諦める」という場面では、例外より null の方が扱いやすいことが多いです。

二つ目は、「リスト自体が null のときも null を返す」ことです。
呼び出し側は「とりあえず安全に要素を取りたい」とだけ考えればよく、
list != null のチェックを毎回書かなくて済みます。

デフォルト値を返すバージョン

「null ではなく、決めたデフォルト値を返したい」こともあります。

public static <T> T getOrDefault(List<T> list, int index, T defaultValue) {
    if (list == null) {
        return defaultValue;
    }
    if (index < 0 || index >= list.size()) {
        return defaultValue;
    }
    T value = list.get(index);
    return value != null ? value : defaultValue;
}
Java

使い方の例です。

List<Integer> numbers = List.of(10, 20);

int x = SafeGets.getOrDefault(numbers, 1, 0);  // 20
int y = SafeGets.getOrDefault(numbers, 5, 0);  // 0(デフォルト)
int z = SafeGets.getOrDefault(null, 0, 0);     // 0
Java

ここでの重要ポイントは、
「“範囲外”と“リストが null”と“値が null”のすべてを、同じようにデフォルト扱いにしている」ことです。

業務として「null は未設定扱いでよい」「なければ 0(空文字など)でよい」と決められるなら、
このパターンはとても書きやすくなります。


Map 向けの安全get

Map が null でも、キーがなくても落ちない

次は Map 版です。

import java.util.Map;

public final class SafeGets {

    private SafeGets() {}

    public static <K, V> V getOrNull(Map<K, V> map, K key) {
        if (map == null) {
            return null;
        }
        return map.get(key); // キーがなければ null
    }
}
Java

使い方はこうです。

Map<String, Integer> scores = Map.of(
        "山田", 80,
        "佐藤", 90
);

Integer a = SafeGets.getOrNull(scores, "山田"); // 80
Integer b = SafeGets.getOrNull(scores, "鈴木"); // null
Integer c = SafeGets.getOrNull(null, "山田");   // null
Java

ここでの重要ポイントは、
「Map 自体が null でも安全に null を返す」ことです。

map.get(key) をそのまま呼ぶと、map が null のときに NullPointerException になりますが、
SafeGets.getOrNull を挟めば、「なければ null」という一貫した挙動になります。

デフォルト値を返すバージョン

Map#getOrDefault もありますが、「Map が null のとき」までは面倒を見てくれません。
そこで、もう一枚ラッパーをかぶせます。

public static <K, V> V getOrDefault(
        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 score = SafeGets.getOrDefault(scores, "鈴木", 0); // 0
Integer score2 = SafeGets.getOrDefault(null, "山田", 0); // 0
Java

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

プロジェクトとして「null は使わず、必ずデフォルト値に正規化する」と決めるなら、
このパターンを徹底するだけで、NPE のリスクがかなり減ります。


Optional を使った安全get

「null を返したくない」場合の書き方

「null を返すのも怖いから、Optional で包みたい」という考え方もあります。

import java.util.List;
import java.util.Map;
import java.util.Optional;

public final class SafeGets {

    private SafeGets() {}

    public static <T> Optional<T> getOptional(List<T> list, int index) {
        if (list == null) {
            return Optional.empty();
        }
        if (index < 0 || index >= list.size()) {
            return Optional.empty();
        }
        return Optional.ofNullable(list.get(index));
    }

    public static <K, V> Optional<V> getOptional(Map<K, V> map, K key) {
        if (map == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(map.get(key));
    }
}
Java

使い方はこうです。

Optional<String> maybeName = SafeGets.getOptional(names, 1);
maybeName.ifPresent(System.out::println);

String nameOrDefault =
        SafeGets.getOptional(names, 10)
                .orElse("不明");
Java

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

一つ目は、「“あるかもしれない/ないかもしれない”を型で表現している」ことです。
Optional<T> を返すことで、「呼び出し側は必ず orElseifPresent などで扱う必要がある」状態になります。

二つ目は、「null を直接返さないことで、“うっかり NPE”を減らせる」ことです。
ただし、Optional を多用しすぎるとコードが重くなるので、「外部公開 API など境界部分で使う」のがバランスのよい使い方です。


安全getをどこまで徹底するか

「例外で気づきたい場所」と「静かに諦めたい場所」を分ける

安全getは便利ですが、何でもかんでも例外を潰して null やデフォルトにしてしまうと、
本来気づくべきバグに気づけなくなることもあります。

たとえば、「絶対に存在するはずのインデックス」「必ず入っているはずのキー」がなかった場合、
本当は例外で落ちてほしい場面もあります。

だからこそ、次のように使い分けるのが大事です。

「ユーザー入力や外部データなど、“なくてもおかしくない”もの」
→ 安全getで null/デフォルトにして、処理を続行する。

「アプリ内部のロジック上、“必ずあるはず”のもの」
→ 通常の get を使い、なければ例外で気づくようにする。

安全getは、「落ちてもおかしくない場所」にだけクッションを敷くイメージで使うと、
コードの意図がとてもクリアになります。


まとめ:安全getユーティリティで身につけてほしい感覚

安全getは、
単に「例外を避けるテクニック」ではなく、
「“ないかもしれない”を前提に、どう振る舞うかを設計する技術」です。

List では「範囲外」「null リスト」を安全に扱う。
Map では「null Map」「キーなし」「値 null」をどう扱うかを決める。
必要に応じてデフォルト値版・Optional 版を用意し、「呼び出し側の責務」をはっきりさせる。
「ここは安全get」「ここはあえて例外でいい」という線引きを、業務の意味とセットで考える。

あなたのコードのどこかに、
if (list != null && index >= 0 && index < list.size()) { ... }
if (map != null && map.get(key) != null) { ... }
のような条件が何度も出てきているなら、
それを一度「安全getユーティリティ」にまとめられないか眺めてみてください。

その小さな整理が、
「落ちてもおかしくない場所に、ちゃんとクッションを敷けるエンジニア」への、
確かな一歩になります。

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