Java Tips | コレクション:null除外

Java Java
スポンサーリンク

「null除外」は“混ざってしまった null を一度で掃除する”ユーティリティ

業務コードを書いていると、こういうコレクションがよく生まれます。

List<String> names = Arrays.asList("山田", null, "佐藤", null, "鈴木");
Java

DBからの結果、外部APIのレスポンス、画面入力のマージなどで、
「null が混ざった List / Set / Map」ができてしまうのは、ほぼ日常です。

問題は、その後の処理で毎回こう書くことになることです。

for (String name : names) {
    if (name != null) {
        // ここで処理
    }
}
Java

null除外ユーティリティは、この「毎回の if (x != null)」をやめて、
「最初に一度だけ null を全部取り除いてしまう」ための小さな道具です。


基本形:List から null を全部取り除く

removeNulls で「null なしの新しい List」を返す

まずは一番よく使う List 版から考えます。
「元の List はそのまま」「null を除いた新しい List を返す」形にするのが安全です。

import java.util.ArrayList;
import java.util.List;

public final class CollectionUtils {

    private CollectionUtils() {}

    public static <T> List<T> removeNulls(List<T> source) {
        if (source == null || source.isEmpty()) {
            return List.of(); // 空の不変List
        }
        List<T> result = new ArrayList<>(source.size());
        for (T e : source) {
            if (e != null) {
                result.add(e);
            }
        }
        return result;
    }
}
Java

使い方はこうなります。

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

List<String> cleaned = CollectionUtils.removeNulls(raw);

System.out.println(cleaned); // [山田, 佐藤, 鈴木]

for (String name : cleaned) {
    // ここでは null チェック不要
    System.out.println(name + " さん");
}
Java

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

一つ目は、「元の List を変更していない」ことです。
removeIf(Objects::isNull) のように“その場で削る”方法もありますが、
呼び出し元が同じ List を他でも使っていると、思わぬ副作用になります。
ユーティリティでは「新しい List を返す」方が安全です。

二つ目は、「null や空 List が来たら、空の不変 List を返す」ことです。
これにより、呼び出し側は null を気にせずに for-each できます。

三つ目は、「戻り値は“null がない”と約束されている」ことです。
removeNulls を通した List に対しては、
if (x != null) を書かなくてよい世界になります。


Set から null を除外する

重複排除+null除外を一度にやる

Set はそもそも「重複を持たない」コレクションですが、
null は普通に入ってきます。

Set<String> raw = new HashSet<>();
raw.add("A");
raw.add(null);
raw.add("B");
Java

これも同じ発想で、「null を除いた新しい Set」を返すユーティリティを用意します。

import java.util.HashSet;
import java.util.Set;

public static <T> Set<T> removeNulls(Set<T> source) {
    if (source == null || source.isEmpty()) {
        return Set.of(); // 空の不変Set
    }
    Set<T> result = new HashSet<>(source.size());
    for (T e : source) {
        if (e != null) {
            result.add(e);
        }
    }
    return result;
}
Java

使い方はこうです。

Set<String> raw = new HashSet<>();
raw.add("A");
raw.add(null);
raw.add("B");

Set<String> cleaned = CollectionUtils.removeNulls(raw);

System.out.println(cleaned); // [A, B] など(null は消えている)
Java

ここでのポイントは、「null除外と重複排除が自然に組み合わさる」ことです。
「とにかく“有効な値だけの集合”が欲しい」というときに、
removeNulls を一発かませば済むようになります。


Map の「キーの null」「値の null」をどう扱うか決める

まずは「値が null のエントリだけ除外する」版

Map の場合、null が入りうる場所は二つあります。
キーが null、値が null。

多くの業務コードでは、「キーは null にしない」「値は null のこともある」
という設計が多いので、まずは「値が null のエントリを除外する」版を作ります。

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

public static <K, V> Map<K, V> removeNullValues(Map<K, V> source) {
    if (source == null || source.isEmpty()) {
        return Map.of(); // 空の不変Map
    }
    Map<K, V> result = new HashMap<>(source.size());
    for (Map.Entry<K, V> e : source.entrySet()) {
        if (e.getValue() != null) {
            result.put(e.getKey(), e.getValue());
        }
    }
    return result;
}
Java

使い方はこうです。

Map<String, String> raw = new HashMap<>();
raw.put("A", "あ");
raw.put("B", null);
raw.put("C", "う");

Map<String, String> cleaned = CollectionUtils.removeNullValues(raw);

System.out.println(cleaned); // {A=あ, C=う}
Java

ここでの重要ポイントは、「null を“キーが存在しない”のと同じ扱いにする」かどうかを、
ユーティリティ側で決めていることです。
removeNullValues を通した Map に対しては、
「値が null という状態は存在しない」とみなせます。

キーの null も排除したい場合

もし「キーも絶対に null にしたくない」設計なら、
キーもチェックする版を用意します。

public static <K, V> Map<K, V> removeNullKeysAndValues(Map<K, V> source) {
    if (source == null || source.isEmpty()) {
        return Map.of();
    }
    Map<K, V> result = new HashMap<>(source.size());
    for (Map.Entry<K, V> e : source.entrySet()) {
        if (e.getKey() != null && e.getValue() != null) {
            result.put(e.getKey(), e.getValue());
        }
    }
    return result;
}
Java

このように、「どこまでを null除外の対象にするか」を
メソッド名と中身で明示しておくと、後から読んだ人にも意図が伝わります。


Stream を使った書き方との違いと使い分け

Stream 版は短いが、「共通ルール」としてはユーティリティに寄せる

Java 8 以降なら、Stream を使って一行で書くこともできます。

List<String> cleaned =
        raw.stream()
           .filter(Objects::nonNull)
           .toList();
Java

これ自体はとてもきれいですが、
あちこちで同じ filter(Objects::nonNull) が出てくると、
「このプロジェクトでは null をどう扱うのか」というルールが散らばってしまいます。

そこで、「null除外は必ず CollectionUtils.removeNulls を通す」と決めておくと、
ルールの場所が一箇所にまとまります。

Stream を使いたければ、ユーティリティの中で使えばよいだけです。

public static <T> List<T> removeNulls(List<T> source) {
    if (source == null || source.isEmpty()) {
        return List.of();
    }
    return source.stream()
                 .filter(Objects::nonNull)
                 .toList();
}
Java

呼び出し側は常に removeNulls だけを知っていればよく、
実装が for 文か Stream かは気にしなくて済みます。


まとめ:null除外ユーティリティで身につけてほしい感覚

null除外ユーティリティは、単に「if (x != null) を減らす小技」ではなく、
「null が混ざったコレクションを、最初に一度だけ“きれいな世界”に変換する」ための道具です。

List なら removeNulls(List) で「null なし List」を作る。
Set なら removeNulls(Set) で「null なし Set」を作る。
Map なら removeNullValuesremoveNullKeysAndValues で、「null を含まないマップ」に正規化する。
「どこまで null を許すか」をユーティリティのメソッド名と実装で明示し、プロジェクトのルールとして固定する。

もしあなたのコードの中に、

if (x != null) { ... }
if (value != null) { list.add(value); }
Java

のようなパターンが何度も出てきているなら、
それを一度「null除外ユーティリティ」にまとめられないか、眺めてみてください。

その小さな整理が、
「null に振り回されず、“きれいなコレクション”を前提にロジックを書けるエンジニア」への、
確かな一歩になります。

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