なぜ StringBuilder / StringBuffer が必要になるのか
Javaの String は「不変(immutable)」です。 一度作った文字列は中身を変更できず、+ で連結すると毎回「新しい String インスタンス」が生成されます。
String s = "";
s = s + "Hello";
s = s + " ";
s = s + "World";
Javaこの書き方は、回数が少ないうちは問題ありませんが、ループの中で何百回・何千回と連結すると、 そのたびに新しい String が作られてメモリと時間を食い、パフォーマンスが悪くなります。
そこで登場するのが「中身を書き換えられる文字列バッファ」である StringBuilder / StringBuffer です。 これらは内部に可変の文字配列を持ち、連結しても毎回新しいオブジェクトを作らずに済むので、効率的な連結ができます。
StringBuilder の基本的な使い方
文字列を効率よく連結する
StringBuilder は、単一スレッド(マルチスレッドで共有しない)状況での文字列連結に向いています。
public class StringBuilderExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
sb.append("!");
String result = sb.toString();
System.out.println(result); // Hello World!
}
}
Javaポイントは、append でどんどん後ろに文字列を足していき、 最後に toString() で「完成した文字列」を取り出す、という流れです。
String の + 連結だと毎回新しいインスタンスが作られますが、 StringBuilder は内部のバッファを伸ばしながら同じインスタンスに書き足していくので、 大量連結のときに圧倒的に効率が良くなります。
ループとの組み合わせで真価を発揮する
特に「ループの中で文字列を組み立てる」場面では、StringBuilder がほぼ必須レベルで効いてきます。
public class LoopConcatExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 5; i++) {
sb.append("Item").append(i);
if (i < 5) {
sb.append(", ");
}
}
String result = sb.toString();
System.out.println(result); // Item1, Item2, Item3, Item4, Item5
}
}
Javaここでは、append をチェーンして「区切り文字付きの一覧」を作っています。 もしこれを String の + で書くと、ループ回数分だけ新しい文字列が生成されてしまい、 データ量が増えるほどパフォーマンスが落ちていきます。
StringBuffer との違いと使い分け
StringBuffer は「スレッドセーフ」
StringBuffer は、基本的な役割は StringBuilder と同じですが、メソッドが同期化されていて「スレッドセーフ」です。 つまり、複数スレッドから同じインスタンスに同時にアクセスしても、内部状態が壊れないように作られています。
StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append("World");
String result = sb.toString();
Javaただし、その分だけロックのオーバーヘッドがあるので、単一スレッドで使う場合は StringBuilder の方が高速です。
実務でのざっくりした使い分け
単一スレッドで文字列を組み立てる → StringBuilder 複数スレッドから同じバッファを触る可能性がある → StringBuffer
とはいえ、最近の設計では「そもそも同じインスタンスを複数スレッドで共有しない」ようにすることが多いので、 「基本は StringBuilder、特殊な事情があるときだけ StringBuffer」くらいの感覚でいて大丈夫です。
String と StringBuilder の変換と注意点
String から始めて、途中から StringBuilder に切り替える
既に String を持っていて、そこからさらに連結したい場合は、 StringBuilder のコンストラクタに初期値として渡せます。
String base = "Hello";
StringBuilder sb = new StringBuilder(base);
sb.append(" ");
sb.append("World");
String result = sb.toString();
System.out.println(result); // Hello World
Java「最初は固定文字列、途中から動的に連結」というパターンでも、 早めに StringBuilder に乗り換えておくと効率が良くなります。
完成したら必ず toString() で取り出す
StringBuilder / StringBuffer はあくまで「文字列を組み立てるためのバッファ」であって、 APIの戻り値やフィールドとしてそのまま外に出すことはあまりしません。
外に渡したいときは、必ず toString() で「完成した String」にしてから渡します。
public String buildMessage(String name) {
StringBuilder sb = new StringBuilder();
sb.append("こんにちは、");
sb.append(name);
sb.append("さん!");
return sb.toString(); // 戻り値は String にする
}
JavaAPIレイヤでは「戻り値は String」、内部での組み立てに StringBuilder、という分け方が自然です。
まとめと小さな練習
String は不変なので、+ 連結を繰り返すとそのたびに新しいインスタンスが生成され、 大量連結ではパフォーマンスが悪くなります。
StringBuilder / StringBuffer は「中身を書き換えられる文字列バッファ」で、 append で効率よく連結し、最後に toString() で完成した文字列を取り出す、という使い方をします。
練習として、次のようなコードを自分で書いてみると感覚がつかめます。
練習1:+ 連結版と StringBuilder 版を比べる
1〜1000までの数字をカンマ区切りで連結する処理を、 String の + だけで書いた版と、StringBuilder で書いた版の両方を作ってみて、 コードの見通しと「なんとなくの重さ」の違いを感じてみてください。
練習2:メッセージテンプレートを組み立てる
ユーザー名・日付・メッセージ本文を受け取って、 「[日付] ユーザー名: 本文」というログ行を StringBuilder で組み立てるメソッドを書いてみてください。
「どこからどこまでを StringBuilder に任せるか」「いつ toString() するか」を意識して書くと、 文字列連結の効率化が自分の設計の一部として自然に組み込まれていきます。
