Java Tips | 文字列処理:伏字処理

Java Java
スポンサーリンク

伏字処理は「内容は伝えつつ、直接は書かない」ための技

マスク処理は「個人情報などを見せていい範囲だけ残す」テクニックでした。
一方で 伏字処理 は、もう少し“表現寄り”のテクニックです。

NGワードを伏せたい。
不適切な単語をそのまま出さずに「○○」にしたい。
レビュー画面では具体名を隠して、雰囲気だけ伝えたい。

こういうときに使うのが伏字処理です。
「情報を守る」というより、
「表現をマイルドにする」「直接的な表現を避ける」ために使われることが多いです。


マスク処理との違いをまず押さえる

マスク処理は「位置ベース」で考えることが多いです。
先頭何文字を残すか、末尾何文字を残すか、などです。

伏字処理は、どちらかというと「内容ベース」です。
特定の単語やフレーズが含まれていたら、それを伏字にする。
例えば、

「バカ」という単語を「○○」にする。
「機密」という単語を「**」にする。

といった具合です。

つまり、マスク処理は「文字列のどの位置を隠すか」、
伏字処理は「文字列のどの“意味(単語)”を隠すか」、
という違いがある、とイメージしておくと分かりやすいです。


基本形:NGワードを丸ごと伏字にする

単純な「完全一致置換」から始める

まずは一番シンプルな、「NGワードを全部同じ伏字にする」パターンから始めます。

public final class Censor {

    private Censor() {}

    public static String censorWord(String text, String ngWord, String replacement) {
        if (text == null) {
            return null;
        }
        if (ngWord == null || ngWord.isEmpty()) {
            return text;
        }
        if (replacement == null) {
            replacement = "○○";
        }
        return text.replace(ngWord, replacement);
    }
}
Java

使い方はこうなります。

String s1 = "あいつはバカだと言った。";
System.out.println(Censor.censorWord(s1, "バカ", "○○"));
// あいつは○○だと言った。

String s2 = "この資料は機密情報です。";
System.out.println(Censor.censorWord(s2, "機密", "**"));
// この資料は**情報です。
Java

ここでのポイントは、「単純な replace でも“伏字っぽいこと”はすぐにできる」という感覚を持つことです。
ただし、このままだと「部分一致しすぎる」問題が出てきます。

例えば、「バカンス」という単語まで「○○ンス」になってしまうかもしれません。
これをどう扱うかが、伏字処理の設計でとても重要になります。


NGワードを複数まとめて伏字にする

まずは「全部同じ伏字」でよい場合

NGワードが1つだけ、ということはあまりありません。
複数の単語をまとめて伏字にしたいことがほとんどです。

import java.util.List;

public final class Censor {

    private Censor() {}

    public static String censorWords(String text, List<String> ngWords, String replacement) {
        if (text == null) {
            return null;
        }
        if (ngWords == null || ngWords.isEmpty()) {
            return text;
        }
        if (replacement == null) {
            replacement = "○○";
        }
        String result = text;
        for (String ng : ngWords) {
            if (ng == null || ng.isEmpty()) {
                continue;
            }
            result = result.replace(ng, replacement);
        }
        return result;
    }
}
Java

使い方はこうです。

List<String> ng = List.of("バカ", "アホ", "機密");

String s = "バカとかアホとか言うな。この資料は機密情報だ。";
System.out.println(Censor.censorWords(s, ng, "○○"));
// ○○とか○○とか言うな。この資料は○○情報だ。
Java

ここでの重要ポイントは、「NGワードのリストを“データ”として持ち、処理ロジックから分離している」ことです。
NGワードの追加・削除は設定ファイルやDB側で行い、
コード側は「リストにあるものを全部伏字にする」という汎用ロジックにしておくと、運用が楽になります。


伏字の“長さ”をどうするか問題

全部同じ記号にするか、文字数に合わせるか

伏字処理でよく悩むのが、「伏字の長さをどうするか」です。

常に「○○」のように固定長にするのか。
元の文字数に合わせて「バカ」なら「○○○」にするのか。

元の文字数に合わせると、
「どのくらいの長さの単語だったか」は分かってしまいますが、
読みやすさや自然さは上がります。

元の長さに合わせる例を見てみましょう。

public final class Censor {

    private Censor() {}

    public static String censorWordWithSameLength(String text, String ngWord, char maskChar) {
        if (text == null) {
            return null;
        }
        if (ngWord == null || ngWord.isEmpty()) {
            return text;
        }
        String replacement = repeat(maskChar, ngWord.length());
        return text.replace(ngWord, replacement);
    }

    private static String repeat(char ch, int count) {
        StringBuilder sb = new StringBuilder(count);
        for (int i = 0; i < count; i++) {
            sb.append(ch);
        }
        return sb.toString();
    }
}
Java

使い方はこうです。

String s = "あいつはバカだ。こいつもバカだ。";
System.out.println(Censor.censorWordWithSameLength(s, "バカ", '*'));
// あいつは***だ。こいつも***だ。
Java

ここでのポイントは、「伏字の長さを元の単語と同じにすることで、“伏せつつもリズムは保つ”」という感覚です。
小説やレビューコメントなど、文章としての自然さを重視したいときに有効です。


正規表現を使った“もう少し賢い”伏字処理

単語境界を意識した伏字

単純な replace だと、「バカンス」まで伏字になってしまう問題がありました。
これを避けるために、正規表現で「単語としての“バカ”だけ」を狙うこともできます。

日本語だと「単語境界」の定義が難しいですが、
例えば「前後がひらがな・カタカナ・漢字・英数字ではない場合だけ伏字にする」といった工夫が考えられます。

ここでは、あくまでイメージとして、簡易的な例を示します。

import java.util.regex.Pattern;
import java.util.regex.Matcher;

public final class CensorRegex {

    private CensorRegex() {}

    public static String censorWordSimpleBoundary(String text, String ngWord, String replacement) {
        if (text == null) {
            return null;
        }
        if (ngWord == null || ngWord.isEmpty()) {
            return text;
        }
        if (replacement == null) {
            replacement = "○○";
        }
        // 前後が「文字・数字・ひらがな・カタカナ・漢字」以外、というざっくりした境界
        String pattern = "(?<![\\p{L}\\p{N}ぁ-んァ-ン一-龥])"
                       + Pattern.quote(ngWord)
                       + "(?![\\p{L}\\p{N}ぁ-んァ-ン一-龥])";
        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(text);
        return m.replaceAll(replacement);
    }
}
Java

使い方はこうです。

String s = "バカと言うな。バカンスに行きたい。";
System.out.println(CensorRegex.censorWordSimpleBoundary(s, "バカ", "○○"));
// ○○と言うな。バカンスに行きたい。
Java

ここでの重要ポイントは、「完全に正確でなくても、“誤爆を減らすための工夫”を入れられる」ということです。
正規表現は強力ですが、複雑にしすぎると保守が難しくなります。
「どこまで厳密にやるか」は、システムの性質や要求に応じて決めるのが現実的です。


伏字処理で必ず意識してほしいこと

一つ目は、「伏字にする“対象”をきちんと定義する」ことです。
NGワードのリストなのか、個人名なのか、商品名なのか。
それによって、完全一致にするのか、部分一致にするのか、正規表現を使うのかが変わります。

二つ目は、「伏字の“形”を統一する」ことです。
○○ にするのか、*** にするのか、[伏字] にするのか。
システム全体で統一しておくと、ユーザーにも開発者にも分かりやすくなります。

三つ目は、「どの層で伏字にするか」を決めることです。
DBに保存する前に伏字にするのか。
画面に出す直前で伏字にするのか。
ログに書く直前で伏字にするのか。

おすすめは、DBには生の値を持ち、
画面やログなど「人の目に触れるところ」で伏字にする設計です。
これにより、「内部処理では正確な値を使える」「外に出るときだけ表現をマイルドにする」という分離ができます。


まとめ:伏字処理ユーティリティで身につけたい感覚

伏字処理は、「情報を完全に隠す」のではなく、
「内容はなんとなく伝えつつ、直接的な表現を避ける」ためのテクニックです。

まずは、String#replace を使った単純な伏字から始めて、
「NGワードのリストをデータとして持つ」「伏字の長さをどうするか決める」
といった設計の感覚を身につけてください。

そのうえで、必要に応じて正規表現を使った“誤爆を減らす伏字”や、
メールアドレス・名前・商品名など、対象ごとの専用伏字ロジックを薄いラッパーとして用意していく。

もしあなたのコードのどこかに、
そのまま出したくない単語や表現を replace で場当たり的に置き換えている箇所があれば、
それを題材にして、ここで作った CensorCensorRegex のようなユーティリティにまとめてみてください。

それだけで、「表現のコントロールができる、実務レベルの文字列処理」に一歩近づけます。

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