Java Tips | 文字列処理:文字列連結

Java Java
スポンサーリンク

文字列連結は「小さなピースを一つのメッセージにまとめる」技

業務システムでは、ログメッセージ、SQL、URL、エラーメッセージ、メール本文など、
「いくつかの値を組み合わせて一つの文字列にしたい」場面がひたすら出てきます。

ここを何も考えずに + で書き散らすと、パフォーマンスが落ちたり、可読性が下がったり、
「どこで何が連結されているのか分からない」状態になりがちです。

だからこそ、「業務で使う文字列連結の“型”をユーティリティとして決めておく」と、
コード全体がかなり落ち着きます。


なぜ + 連結だけに頼るとつらくなるのか

小さな例では便利、大きくなると一気に読みにくくなる

まず、+ 連結自体は悪ではありません。小さな例ではむしろ読みやすいです。

String message = "Hello, " + name + "!";
Java

これは全然アリです。

問題になるのは、こういうパターンです。

String sql =
        "SELECT * FROM users " +
        "WHERE status = '" + status + "' " +
        "AND created_at >= '" + from + "' " +
        "AND created_at < '" + to + "'";
Java

一見読めそうですが、値の部分に ' が入ったら壊れますし、
どこまでが固定文字列で、どこからが変数なのかがパッと見で分かりづらくなります。

さらに、ループの中で + 連結を繰り返すと、String がイミュータブル(変更不可)であるがゆえに、
内部で何度も新しいインスタンスが生成され、パフォーマンスにも悪影響が出ます。

ここで登場するのが、StringBuilder と「連結をユーティリティに閉じ込める」という発想です。


基本の武器:StringBuilder を素直に使う

「何度も足していく」なら、最初から StringBuilder にする

StringBuilder は、「文字列を何度も追加していく」ためのクラスです。
内部で可変のバッファを持っていて、append するたびに中身を書き足していきます。

StringBuilder sb = new StringBuilder();
sb.append("Hello, ");
sb.append(name);
sb.append("!");
String message = sb.toString();
Java

これを見て、「+ より長くてダルい」と感じるのは自然です。
なので、業務では「パターン化してユーティリティに閉じ込める」のがポイントになります。


例題:ログメッセージを安全に連結するユーティリティ

「キー=値」を並べるパターンを固める

ログでよくあるのが、こんな書き方です。

System.out.println("userId=" + userId + ", action=" + action + ", result=" + result);
Java

これを毎回手書きするのではなく、「キー=値をカンマ区切りで並べる」ユーティリティを作ります。

public final class LogStrings {

    private LogStrings() {}

    public static String kv(String key, Object value) {
        return key + "=" + String.valueOf(value);
    }

    public static String joinWithComma(String... parts) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            if (i > 0) {
                sb.append(", ");
            }
            sb.append(parts[i]);
        }
        return sb.toString();
    }
}
Java

使い方はこうなります。

String log = LogStrings.joinWithComma(
        LogStrings.kv("userId", userId),
        LogStrings.kv("action", action),
        LogStrings.kv("result", result)
);

System.out.println(log);
// userId=u-001, action=LOGIN, result=SUCCESS
Java

ここで深掘りしたい重要ポイントは、「“どう連結するか”のルールをユーティリティに閉じ込めている」ことです。

カンマの前後にスペースを入れるか、最後にカンマを付けないか、null をどう表示するか、
そういった細かいルールを一箇所に集約できるので、ログのフォーマットが全体で揃います。


例題:区切り文字で連結する join ユーティリティ

String.join でもいいが、自前で持つと拡張しやすい

Java 8 以降には String.join があります。

String s = String.join(", ", "A", "B", "C"); // "A, B, C"
Java

これをそのまま使ってもよいのですが、業務では「null をどう扱うか」「空文字を飛ばすか」など、
もう一歩踏み込んだルールを決めたいことが多いです。

そこで、簡単な join ユーティリティを作ります。

public final class Strings2 {

    private Strings2() {}

    public static String join(String delimiter, Iterable<?> values) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Object v : values) {
            if (v == null) {
                continue; // null はスキップする方針
            }
            if (!first) {
                sb.append(delimiter);
            } else {
                first = false;
            }
            sb.append(v);
        }
        return sb.toString();
    }
}
Java

使い方はこうです。

List<String> names = java.util.List.of("山田", null, "佐藤", "鈴木");
String joined = Strings2.join(" / ", names);
System.out.println(joined); // 山田 / 佐藤 / 鈴木
Java

ここでのポイントは、「“null を飛ばす”というルールを join 側に持たせている」ことです。
呼び出し側は「null チェックしてから add する」などを意識せずに済みます。

こういう「連結のルール」をユーティリティに閉じ込めると、
画面表示、ログ、CSV など、いろんな場面で同じ感覚で文字列連結ができるようになります。


例題:プレフィックス・サフィックス付きの連結

「[] で囲む」「{} で囲む」をパターン化する

ログやメッセージで、「何かを [] で囲む」「<> で囲む」といった表現もよく出てきます。

String s = "[" + value + "]";
Java

これも小さいうちはいいのですが、あちこちに散らばると微妙に表記揺れが出ます。
そこで、こんなユーティリティを用意します。

public final class Brackets {

    private Brackets() {}

    public static String square(Object value) {
        return "[" + String.valueOf(value) + "]";
    }

    public static String angle(Object value) {
        return "<" + String.valueOf(value) + ">";
    }

    public static String curly(Object value) {
        return "{" + String.valueOf(value) + "}";
    }
}
Java

使い方はこうです。

String log = "user " + Brackets.square(userId) + " logged in.";
System.out.println(log);
// user [u-001] logged in.
Java

ここでの重要ポイントは、「“見た目のルール”をコード上で名前にしてしまう」ことです。
Brackets.square と書いてあれば、「[] で囲む」という意図が一目で分かります。

これを積み重ねていくと、「文字列連結=ただの + の集まり」ではなく、
「意味のあるパーツを組み合わせてメッセージを作る」という感覚に変わっていきます。


パフォーマンスの話を、初心者向けにちゃんと整理する

「ループの中の + は StringBuilder に置き換える」が基本ライン

よく言われるのが、「+ 連結は遅いから全部 StringBuilder にしろ」という極論ですが、
実務的には「ループの中で何度も連結するなら StringBuilder、それ以外は読みやすさ優先で + でもよい」くらいの感覚で十分です。

例えば、こんなコードは要注意です。

String s = "";
for (String part : parts) {
    s = s + part; // 毎回新しい String が生成される
}
Java

これを StringBuilder にするとこうなります。

StringBuilder sb = new StringBuilder();
for (String part : parts) {
    sb.append(part);
}
String s = sb.toString();
Java

ここで深掘りしたいのは、「“何回連結するか”が重要」ということです。
1〜2回の連結なら + で十分ですが、100回、1000回と増えていくと、
StringBuilder を使うかどうかで差が出てきます。

そして、その StringBuilder の使い方も、できるだけユーティリティに閉じ込めてしまえば、
呼び出し側は「ループの中で join を呼ぶ」だけで済むようになります。


まとめ:文字列連結ユーティリティで身につけたい感覚

文字列連結は、「ただ + でつなぐ」だけの話ではなく、
「どんなルールで」「どんな見た目で」「どれくらいの回数を」「どこに責務を持たせて」つなぐか、という設計の話です。

押さえておきたい感覚は、まず「小さな連結は + でよいが、繰り返しや複雑な連結は StringBuilder ベースのユーティリティに寄せる」こと。
次に、「ログ用の kv、区切り文字付き join、括弧で囲むなど、“よく出るパターン”を名前付きのメソッドにしてしまう」こと。
そして、「連結のルール(null の扱い、区切り、フォーマット)をユーティリティに閉じ込めることで、コード全体の見た目と挙動を揃える」ことです。

もしあなたのプロジェクトのどこかに、"userId=" + userId + ", action=" + action + ... のようなコードが散らばっているなら、
その一つを題材にして、ここで作った LogStrings.kvStrings2.join に置き換えてみてください。
それだけで、「読みやすくて、変更に強くて、パフォーマンスも安定した文字列連結」に、一段レベルアップできます。

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