Java Tips | 文字列処理:文字列比較IgnoreNull

Java Java
スポンサーリンク

「文字列比較IgnoreNull」は null に振り回されないための小さなルール

業務システムでは、DBの値・画面入力・外部APIのレスポンスなど、
「null かもしれない String」を比較する場面が山ほど出てきます。

そのたびに
if (s != null && s.equals("OK"))
のようなコードを書いていると、読みづらいし、どこかで必ず書き忘れます。

そこで、「null が来ても絶対に落ちない」「比較ルールが一目でわかる」
という形にまとめたのが、ここでいう 文字列比較IgnoreNull なユーティリティです。


まずは素の equals と null の関係を整理する

String#equals は、呼び出し元が null だと NullPointerException になります。

String a = null;
String b = "test";

// これは NPE
boolean x = a.equals(b);

// これは安全
boolean y = b.equals(a); // false
Java

この「どっちを左に書くかで落ちたり落ちなかったりする」という性質が、
初心者にとってかなりの落とし穴になります。

だからこそ、「null を気にせずに equals したい」という欲求が生まれます。
それを形にしたのが、null を無視(というか安全に扱う)比較ユーティリティです。


基本形:null セーフな equalsIgnoreNull を作る

一番シンプルな方針は
「どちらかが null でも例外を出さず、内容が同じなら true」
というメソッドを用意することです。

public final class StringCompareUtil {

    private StringCompareUtil() {}

    public static boolean equalsIgnoreNull(String a, String b) {
        if (a == null && b == null) {
            return true;
        }
        if (a == null || b == null) {
            return false;
        }
        return a.equals(b);
    }
}
Java

このルールだと、次のように動きます。

System.out.println(StringCompareUtil.equalsIgnoreNull(null, null));   // true
System.out.println(StringCompareUtil.equalsIgnoreNull(null, "A"));    // false
System.out.println(StringCompareUtil.equalsIgnoreNull("A", null));    // false
System.out.println(StringCompareUtil.equalsIgnoreNull("A", "A"));     // true
System.out.println(StringCompareUtil.equalsIgnoreNull("A", "B"));     // false
Java

ここで重要なのは、二つの点です。

一つ目は、「null 同士は等しいとみなす」と決めていることです。
これを true にするか false にするかは設計次第ですが、
「どちらも値が設定されていない状態」として true にするのは、
フォーム比較や差分チェックなどで直感的に扱いやすいことが多いです。

二つ目は、「null が片方だけなら必ず false」と決めていることです。
このルールをユーティリティに閉じ込めておくことで、
呼び出し側は equalsIgnoreNull(a, b) と書くだけで、
null チェックと equals を一度に済ませられます。


Java 標準の Objects.equals との違いを理解する

実は Java 7 以降には、すでに似た発想のメソッドが標準で用意されています。
java.util.Objects.equals(a, b) は、まさに「null セーフな equals」です。

import java.util.Objects;

System.out.println(Objects.equals(null, null));   // true
System.out.println(Objects.equals(null, "A"));    // false
System.out.println(Objects.equals("A", null));    // false
System.out.println(Objects.equals("A", "A"));     // true
System.out.println(Objects.equals("A", "B"));     // false
Java

先ほど作った equalsIgnoreNull とほぼ同じ挙動です。

では、わざわざ自前ユーティリティを作る意味はあるのか、という話になりますが、
ここが設計の面白いところです。

例えば、次のような「自分たちのルール」を追加したい場合、
自前ユーティリティにする価値が出てきます。

特定の比較では「前後の空白を無視したい」
特定の比較では「大文字小文字を無視したい」
特定の比較では「null を空文字と同じ扱いにしたい」

こういった「プロジェクト固有の比較ルール」を、
equalsIgnoreNull という名前の下に閉じ込めてしまう、という発想です。


応用1:大文字小文字を無視しつつ null セーフにする

よくある要件に「大文字小文字を区別しない比較」があります。
String#equalsIgnoreCase は便利ですが、やはり呼び出し元が null だと NPE になります。

そこで、null セーフな equalsIgnoreCase を用意します。

public static boolean equalsIgnoreNullIgnoreCase(String a, String b) {
    if (a == null && b == null) {
        return true;
    }
    if (a == null || b == null) {
        return false;
    }
    return a.equalsIgnoreCase(b);
}
Java

これで、ユーザー入力とマスタ値の比較などを、
安心して一行で書けるようになります。

String input = null;          // ユーザー入力(null の可能性あり)
String expected = "YES";

if (StringCompareUtil.equalsIgnoreNullIgnoreCase(input, expected)) {
    // YES とみなす処理
}
Java

ここでのポイントは、「null チェック」と「大文字小文字無視」を
毎回バラバラに書かない、ということです。
一つの名前付きルールにまとめることで、
コードを読む人が「この比較はどういう意図なのか」を一瞬で理解できます。


応用2:「null と空文字を同じとみなす」比較ルール

業務によっては、「null も空文字も“未入力”として同じ扱いにしたい」ことがあります。
その場合は、比較前に両方を正規化してしまうのが分かりやすいです。

public static boolean equalsTreatNullAsEmpty(String a, String b) {
    String na = (a == null) ? "" : a;
    String nb = (b == null) ? "" : b;
    return na.equals(nb);
}
Java

このルールだと、次のようになります。

System.out.println(equalsTreatNullAsEmpty(null, null)); // true
System.out.println(equalsTreatNullAsEmpty(null, ""));   // true
System.out.println(equalsTreatNullAsEmpty("", null));   // true
System.out.println(equalsTreatNullAsEmpty("A", ""));    // false
Java

ここでの重要ポイントは、「null をどう解釈するかを“明示的に決める”」ことです。
「null は空文字と同じ」「null は空文字とは違う」
どちらもありえますが、プロジェクト内でルールを統一しておくと、
バグの温床になりにくくなります。


実務での使いどころと、ユーティリティにする意味

文字列比較IgnoreNull系のユーティリティは、次のような場面でよく効きます。

フォーム入力値とDB値の比較(どちらも null の可能性がある)
外部APIレスポンスのステータス文字列の判定
設定値(環境変数・プロパティファイル)と固定値の比較

これらをすべて

if (value != null && value.equals("OK")) { ... }
Java

のように書いていると、
どこかで value.equals("OK") とだけ書いて NPE を踏みます。

一方で、

if (StringCompareUtil.equalsIgnoreNull(value, "OK")) { ... }
Java

と書くスタイルに統一しておけば、
「ここは null セーフな比較をしている」ということが一目で分かり、
かつ NPE の心配もほぼなくなります。


まとめ:「比較ルールに名前をつける」とコードが一気に読みやすくなる

文字列比較IgnoreNullは、単に「NPE を避ける小技」ではなく、
「自分たちの比較ルールに名前をつけて、コードに刻む」ためのユーティリティです。

null 同士を等しいとみなすかどうか。
大文字小文字を無視するかどうか。
null と空文字を同じとみなすかどうか。

こういった判断を毎回 if 文で書くのではなく、
equalsIgnoreNullequalsIgnoreNullIgnoreCase のような
はっきりした名前のメソッドに閉じ込めてしまう。

もしあなたのプロジェクトの中に、
null チェックと equals が何度も何度も繰り返されているなら、
それを一度「文字列比較IgnoreNullユーティリティ」にまとめてみてください。

その小さな整理が、
「null に振り回されない、意図の伝わる文字列比較を書けるエンジニア」への、
確かな一歩になります。

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