Java Tips | 文字列処理:部分一致

Java Java
スポンサーリンク

部分一致は「含まれているかどうか」を調べる基本テクニック

業務システムで「部分一致」はめちゃくちゃよく出てきます。
商品名に「りんご」が含まれているデータだけ検索したい。
エラーメッセージに特定のキーワードが含まれているかで処理を分けたい。
ログの1行の中に「ERROR」が入っているかをチェックしたい。

こういうときに使うのが「部分一致」です。
ざっくり言うと「ある文字列の中に、別の文字列が含まれているかどうか」を調べる処理です。
Javaでは、これをユーティリティメソッドとしてまとめておくと、あちこちで再利用できてコードがかなりスッキリします。


Javaの基本メソッドで部分一致を押さえる

containsで「含まれているか」をシンプルに判定する

一番基本になるのは String#contains です。
「この文字列の中に、指定した文字列が含まれているか」を true/false で返してくれます。

public final class StringMatchUtil {

    private StringMatchUtil() {}

    public static boolean contains(String text, String keyword) {
        if (text == null || keyword == null) {
            return false;
        }
        return text.contains(keyword);
    }
}
Java

使い方はとてもシンプルです。

System.out.println(StringMatchUtil.contains("りんごジュース", "りんご")); // true
System.out.println(StringMatchUtil.contains("みかんジュース", "りんご")); // false
System.out.println(StringMatchUtil.contains("ERROR: something wrong", "ERROR")); // true
System.out.println(StringMatchUtil.contains("INFO: ok", "ERROR"));              // false
Java

ここでまず大事なのは、「null をどう扱うかを決める」ことです。
上の実装では、textkeyword のどちらかが null なら false を返す、という方針にしています。
「null は一致しないものとして扱う」というルールを決めておくと、呼び出し側のコードが安定します。

indexOfで「どこに含まれているか」も知る

contains は true/false だけですが、
「どの位置に含まれているか」を知りたいときは indexOf を使います。

public static int indexOf(String text, String keyword) {
    if (text == null || keyword == null) {
        return -1;
    }
    return text.indexOf(keyword); // 見つからなければ -1
}
Java
System.out.println(indexOf("りんごジュース", "りんご")); // 0
System.out.println(indexOf("おいしいりんごジュース", "りんご")); // 4
System.out.println(indexOf("みかんジュース", "りんご")); // -1
Java

indexOf は「見つからなければ -1」という約束になっているので、
「0以上なら含まれている」「-1なら含まれていない」と判定できます。
位置が必要ない場面では contains、位置も使いたい場面では indexOf、という使い分けを覚えておくと便利です。


前方一致・後方一致も「部分一致の仲間」として押さえる

startsWithで「この文字列で始まっているか」を判定する

「部分一致」の一種として、前方一致(前から一致)もよく使います。
Javaでは startsWith で判定できます。

public static boolean startsWith(String text, String prefix) {
    if (text == null || prefix == null) {
        return false;
    }
    return text.startsWith(prefix);
}
Java
System.out.println(startsWith("ABC123", "ABC")); // true
System.out.println(startsWith("XYZ123", "ABC")); // false
Java

例えば「商品コードが ‘ABC’ で始まるものだけ抽出したい」といった場面で使えます。

endsWithで「この文字列で終わっているか」を判定する

同じように、後方一致(後ろから一致)は endsWith です。

public static boolean endsWith(String text, String suffix) {
    if (text == null || suffix == null) {
        return false;
    }
    return text.endsWith(suffix);
}
Java
System.out.println(endsWith("report.csv", ".csv")); // true
System.out.println(endsWith("image.png", ".csv"));  // false
Java

ファイル拡張子のチェックや、特定のサフィックスを持つIDの判定など、実務でかなり出番があります。


大文字小文字を無視した部分一致

toLowerCase / toUpperCase で正規化してから比較する

英字を扱うときに必ず出てくるのが「大文字小文字を区別するかどうか」です。
例えば "Error""ERROR""error" も、全部「エラー」として扱いたい、というケースはよくあります。

その場合は、両方を同じ大文字・小文字に揃えてから contains すればOKです。

public static boolean containsIgnoreCase(String text, String keyword) {
    if (text == null || keyword == null) {
        return false;
    }
    String t = text.toLowerCase();
    String k = keyword.toLowerCase();
    return t.contains(k);
}
Java
System.out.println(containsIgnoreCase("ERROR: something", "error")); // true
System.out.println(containsIgnoreCase("Error: something", "ERROR")); // true
System.out.println(containsIgnoreCase("Info: ok", "ERROR"));         // false
Java

ここで深掘りしたいのは「正規化(normalize)」という考え方です。
判定ロジックそのものは contains と同じですが、その前に

すべて小文字にする(または大文字にする)

という前処理を挟んでいます。
この「前処理+本処理」という分け方は、部分一致に限らず、
文字列処理全般でとても重要なパターンです。


全角・半角や空白をどう扱うかを決める

「見た目として一致していればOK」にしたい場合

日本語のシステムでは、「全角・半角」「全角スペース・半角スペース」が絡んできます。
例えば、ユーザーが「 りんご」(先頭に全角スペース)と入力しても、
「りんご」と一致したとみなしたい、ということがあります。

その場合も、やることは「正規化してから部分一致」です。
簡単な例として、「前後の空白を削る」「全角英数字を半角にする」などを行ってから contains します。

public static String normalizeSimple(String text) {
    if (text == null) {
        return null;
    }
    String trimmed = text.trim(); // 前後の空白を削る(全角スペースは別途対応が必要な場合も)
    // ここで本格的な全角→半角変換をするなら、外部ライブラリや自前実装が必要
    return trimmed;
}

public static boolean containsNormalized(String text, String keyword) {
    if (text == null || keyword == null) {
        return false;
    }
    String t = normalizeSimple(text);
    String k = normalizeSimple(keyword);
    return t.contains(k);
}
Java

ここでのポイントは、「どこまで正規化するかは要件次第」ということです。
全角・半角・濁点・カタカナ/ひらがななど、
全部を吸収しようとすると一気に難しくなります。

まずは「前後の空白を削る」「大文字小文字を揃える」くらいから始めて、
必要になったら正規化の範囲を広げていく、という段階的な考え方が現実的です。


実務で使える部分一致ユーティリティの形

「nullの扱い」と「正規化の方針」を決めておく

業務・実務で本当に使えるユーティリティにするには、
次のような点をきちんと決めておくことが大事です。

null が来たらどうするか(falseにするのか、例外にするのか)
大文字小文字を区別するか
前後の空白を無視するか
全角・半角をどこまで吸収するか

例えば、「ログメッセージにキーワードが含まれているか」を判定するユーティリティを、
大文字小文字と前後の空白を無視する形で書くと、こんな感じになります。

public final class LogMatchUtil {

    private LogMatchUtil() {}

    public static boolean containsKeyword(String logLine, String keyword) {
        if (logLine == null || keyword == null) {
            return false;
        }
        String lineNorm = logLine.trim().toLowerCase();
        String keyNorm  = keyword.trim().toLowerCase();
        return lineNorm.contains(keyNorm);
    }
}
Java
System.out.println(
    LogMatchUtil.containsKeyword("  ERROR: something bad  ", "error")
); // true

System.out.println(
    LogMatchUtil.containsKeyword("INFO: ok", "error")
); // false
Java

このように、「用途に合わせて“どう正規化してから部分一致するか”を決める」のが、
実務ユーティリティ設計のコツです。


まとめ:部分一致ユーティリティで身につけたい感覚

部分一致は、「この文字列の中に、別の文字列が含まれているか」を調べる、
文字列処理の超基本テクニックです。

Javaではまず

contains でシンプルな部分一致
indexOf で位置も知る
startsWith / endsWith で前方・後方一致

を押さえ、そのうえで

大文字小文字を無視したいなら toLowerCase / toUpperCase で正規化
前後の空白や全角・半角をどう扱うかを決めてから比較

という「正規化+部分一致」のパターンを身につけておくと、
実務での検索・フィルタリング・ログ判定などが一気に書きやすくなります。

もしあなたのコードのどこかに、
if (text.contains("ERROR")) { ... } のような生の条件が散らばっているなら、
それを一度 LogMatchUtil.containsKeyword のようなユーティリティにまとめてみてください。

その小さな整理が、「読みやすくて変更に強い文字列処理」を書けるエンジニアへの、確かな一歩になります。

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