なぜ StringBuilder が必要になるのか
まず前提として、String は「イミュータブル(不変)」です。
一度作られた String の中身は絶対に変わりません。
その結果、こういうコードを書くと
String s = "";
for (int i = 0; i < 10000; i++) {
s = s + i;
}
Java毎回 s + i のたびに「新しい String オブジェクト」が生成され、
古い String は捨てられていきます。
ループ回数が多いと、
「文字列をちょっと足したいだけなのに、オブジェクトを大量に作っては捨てる」
というムダな処理が発生します。
この問題を解決するために用意されているのが StringBuilder です。StringBuilder は「中身を書き換えられる、文字列バッファ」です。
StringBuilder の基本的な使い方
文字列連結の典型例
StringBuilder の代表的な使い方を、String と比較で見てみましょう。
まず、よくない書き方(String での連結):
String result = "";
for (int i = 0; i < 5; i++) {
result = result + i;
}
System.out.println(result); // 01234
Javaこれを StringBuilder で書き直すと:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append(i);
}
String result = sb.toString();
System.out.println(result); // 01234
Javaここで重要なのは
StringBuilder に対しては append でどんどん足していく
最後に toString() で「完成した String」に変換する
という流れです。
ループ中では String を何度も作らず、StringBuilder の中身(バッファ)を書き換えながら、一つの結果を作り上げていきます。
StringBuilder が「速くなる」理由(深掘り)
String は「毎回新しいオブジェクト」、StringBuilder は「中身だけ更新」
String は不変なので、result = result + i; の実体は
前の result の文字列と i を連結した、新しい String を作る
result 変数を、その新しい String に差し替える
という動作です。
例えば 3 回ループしたときのイメージはこうです。
1回目: "" + 0 → "0"
2回目: "0" + 1 → "01"
3回目: "01" + 2 → "012"
それぞれ "0", "01", "012" という別オブジェクトが作られます。
一方で StringBuilder は「自分の中のバッファを書き換える」だけです。
内部に「可変の文字配列」を持っていて、append されるたびにその配列に追記していきます。
だから、大量連結するときには
String で毎回新規生成 → 無駄が多いStringBuilder の1つのインスタンスを使い回す → 無駄が少ない
という差になります。
どんなときに StringBuilder を使うべきか
目安:ループでどんどん文字列を足しているなら検討
初心者のうちは、ざっくりこう考えてください。
1〜数回程度の連結"Hello, " + name のようなちょっとしたもの
→ + をそのまま使って OK(読みやすさ優先)
ループの中で何度も連結している
1行ずつログをつなげる
大量のデータから1つの文字列を組み立てる
→ StringBuilder を使うとよい
例えば、CSV 1 行を構築する例を考えてみます。
悪い例(String 連結):
String line = "";
for (String value : values) {
if (!line.isEmpty()) {
line += ",";
}
line += value;
}
Java良い例(StringBuilder):
StringBuilder sb = new StringBuilder();
for (String value : values) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(value);
}
String line = sb.toString();
Javasb.length() で現在の長さをチェックできるので、
「先頭以外の前にカンマを付ける」などの処理も書きやすくなります。
StringBuilder の主なメソッドとよくある使い方
append:なんでも足す
append は、文字列だけでなく、いろいろな型を直接渡せます。
StringBuilder sb = new StringBuilder();
sb.append("合計: ");
sb.append(10);
sb.append(" 円");
String s = sb.toString(); // "合計: 10 円"
Javaint, long, double, boolean, char などもそのまま渡せるので、String.valueOf(...) をいちいち書かなくて済みます。
insert:途中に差し込む
途中に文字を入れたいときは insert を使います。
StringBuilder sb = new StringBuilder("abcde");
sb.insert(2, "X"); // インデックス2の位置に挿入
System.out.println(sb.toString()); // "abXcde"
Javadelete や replace などもありますが、
初心者のうちは append と toString を押さえておけば十分です。
length:現在の長さを確認
length() は「現在の文字数」です。
if (sb.length() > 0) {
sb.append(",");
}
Javaのように、「最初の要素かどうか」の判定などでよく使います。
String と StringBuilder の変換
String → StringBuilder
既に持っている String に続きの文字を効率的に足したいときは、StringBuilder に変換してから作業します。
String s = "Hello";
StringBuilder sb = new StringBuilder(s);
sb.append(", World!");
String result = sb.toString();
System.out.println(result); // "Hello, World!"
Javaこのように、「一旦 StringBuilder として編集 → 最後に String に戻す」パターンです。
StringBuilder → String
toString() で、完成した中身を String として取り出します。
String result = sb.toString();
Javaここで返ってきた String は、もちろんイミュータブルな普通の String です。
StringBuffer との違い(軽く触れておく)
StringBuffer はスレッドセーフ版
StringBuilder によく似たクラスとして StringBuffer があります。
StringBuffer は内部のメソッドが同期化されていて、
複数スレッドから同時に触られても安全な設計になっています。
一方 StringBuilder は同期化されていないので、
単一スレッドでの利用を前提にした、より軽量・高速なクラスです。
現代の Java コードでは、
単一スレッド内で文字列を構築 → StringBuilder を使う
どうしてもマルチスレッドで同じバッファを共有する必要がある → StringBuffer も検討
という使い分けが一般的です。
初心者のうちは
「普通は StringBuilder を使う。StringBuffer は歴史的事情で残っているっぽい」
くらいの理解で問題ありません。
まとめ:StringBuilder をいつ、どう使うか
StringBuilder を一言で表すと、
「たくさん文字を足したいときの、効率の良い下書き用バッファ」
です。
意識しておきたいポイントは:
String はイミュータブルなので、連結のたびに新しいオブジェクトができる
ループでの大量連結は StringBuilder に任せると効率が良いappend で何でも足して、最後に toString() で完成品の String を取り出す
