StringBuffer はどんなクラスか(まず全体像)
StringBuffer は、ざっくり言うと
「中身を書き換えられる文字列バッファ」
「スレッドセーフな StringBuilder」
です。
役割自体は StringBuilder とほぼ同じで、
文字列をちょっとずつ足していく
途中に挿入する
一部を書き換える
といった「可変な文字列操作」を効率よく行うためのクラスです。
違いは、「マルチスレッドから同時に触られても一応安全」という点です。
ただし、その分ロック(同期)がかかるので、単一スレッドで使うなら通常は StringBuilder の方が速くておすすめです。
今の Java の実務では「StringBuffer が主役になるケースは少ない」が、
古いコードやライブラリではよく出てきます。
なので「何者で、何が違って、どういうときに選ぶのか」を整理しておく価値は十分あります。
基本の使い方:StringBuilder とほぼ同じ
文字列連結の例
まずは素朴な例から。
String で書くとこうなります。
String s = "";
for (int i = 0; i < 5; i++) {
s = s + i;
}
System.out.println(s); // 01234
Javaこれを StringBuffer で書き直すと、こうなります。
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 5; i++) {
sb.append(i);
}
String result = sb.toString();
System.out.println(result); // 01234
JavaStringBuilder とほぼ同じ書き方です。
append でどんどん末尾に追加していき、
最後に toString() で完成した String を取り出します。
String と違い、append で毎回新しいオブジェクトを作るのではなく、
内部のバッファを書き換えながら文字列を組み立てていきます。
StringBuffer と StringBuilder の違い(ここが一番重要)
同じ役割だが、「同期化されているか」が違う
StringBuffer と StringBuilder の違いは、一言でいうと「同期化(スレッドセーフ)かどうか」です。
StringBuffer
すべてのメソッドが同期化されている(synchronized)
複数スレッドから同時に触られても、一応整合性が保たれる
StringBuilder
同期化されていない
単一スレッドでの利用を想定していて、その分軽くて速い
イメージとしては
「共有ノートを、みんなで順番待ちして書き込む」のが StringBuffer
「自分だけのノートに好き勝手書く」のが StringBuilder
です。
同期化されるということは、「そのメソッドを呼んでいる間はロックを取る」ということです。
その分オーバーヘッドが増えるので、
マルチスレッドで同じインスタンスを共有しないなら、わざわざ StringBuffer にするメリットはほぼありません。
今どきのコードでは
特別な理由がなければ StringBuilder を使う
古いコードに StringBuffer がいても「そういう時代だった」と理解しつつ読む
というスタンスが現実的です。
なぜ昔は StringBuffer がよく使われていたのか
StringBuilder が登場する前の事情
StringBuilder が導入されたのは Java 5 からです。
それ以前は、「可変文字列バッファ」としては StringBuffer しかありませんでした。
つまり
昔のコード(Java 1.4 時代など)では「可変文字列 = StringBuffer」だった
その流れで、今でも StringBuffer が残っているプロジェクトがある
という歴史があります。
その後、「単一スレッドでの文字列構築ならロックいらなくない?」ということで
より軽量な StringBuilder が登場しました。
今の推奨は
単一スレッドで使う → StringBuilder
本当に複数スレッドで同じインスタンスを触る必要がある → (よく考えたうえで)StringBuffer も検討
という使い分けです。
具体例でイメージする:ログメッセージを組み立てる
String 連結との比較
例えば、複数の情報からログメッセージを作るケースを考えます。
String でよくある書き方:
String msg = "user=" + userId
+ ", ip=" + ipAddress
+ ", action=" + action;
System.out.println(msg);
Javaこれは回数も少なく、+ で十分です。
わざわざ StringBuffer や StringBuilder を使う必要はありません。
一方、こういうコードになってきたらどうでしょう。
String msg = "";
for (Event e : events) {
msg += "user=" + e.getUserId()
+ ", action=" + e.getAction()
+ ", time=" + e.getTime()
+ "\n";
}
System.out.println(msg);
Javaループのたびに msg に + 連結しているので、
その回数分だけ新しい String が生成されてしまいます。
これを StringBuffer で書き換えると、こうなります。
StringBuffer sb = new StringBuffer();
for (Event e : events) {
sb.append("user=").append(e.getUserId())
.append(", action=").append(e.getAction())
.append(", time=").append(e.getTime())
.append("\n");
}
String msg = sb.toString();
System.out.println(msg);
JavaStringBuilder でも全く同じ書き方ができますが、
古いコードや、マルチスレッドに気を使ったコードでは StringBuffer が出てきます。
StringBuffer の主なメソッドと使い方
append で何でも足せる
append は StringBuilder と同様に、いろいろな型を受け取れます。
StringBuffer sb = new StringBuffer();
sb.append("合計: ");
sb.append(10);
sb.append(" 円");
System.out.println(sb.toString()); // 合計: 10 円
Javaint, long, double, boolean, char など、ほぼ何でもそのまま渡せます。
insert / delete / replace など、編集系も一通りある
部分挿入:
StringBuffer sb = new StringBuffer("abcde");
sb.insert(2, "X"); // インデックス 2 に "X" を挿入
System.out.println(sb.toString()); // abXcde
Java削除:
sb.delete(1, 3); // インデックス 1〜2 を削除
System.out.println(sb.toString());
Java置き換え:
sb.replace(0, 2, "YY");
Javaただし初心者のうちは、
まずは
コンストラクタで初期文字列を渡す(必要なら)
append でどんどん構築
最後に toString
この3つがきちんと使えれば十分です。
マルチスレッドでのイメージ:本当に StringBuffer が必要?
「複数スレッドから1つのバッファを共有する」状況
StringBuffer の「売り」は、メソッドが synchronized で
複数スレッドから同時にアクセスされても一応安全、という点です。
ただ、現実的には
複数スレッドから同じバッファに同時に文字を足す
という状況自体が、設計として微妙なことも多いです。
多くの場合、
各スレッドごとに StringBuilder を持つ
最後にメインスレッドで結果をまとめる
といった設計の方が、シンプルで安全で高速です。
つまり、「スレッドセーフだから StringBuffer を使おう」と安易に飛びつく前に、
そもそも同じインスタンスを共有する必要があるのか?
各スレッドに専用バッファを持たせた方が設計として自然では?
を一度考えたほうがいい場面が多いです。
まとめ:初心者としての StringBuffer の捉え方
StringBuffer を、今のうちにこう整理しておくといいです。
役割は StringBuilder とほぼ同じ「可変の文字列バッファ」
違いは「メソッドが synchronized でスレッドセーフ」かどうか
単一スレッドのコードでは、ほぼ常に StringBuilder の方が良い選択
歴史的な経緯で古いコードにたくさん出てくるので、「読み解く力」のために知っておく
実務で新しくコードを書くときは、
まずは StringBuilder を使う
既存の StringBuffer を見たら、「スレッド共有前提だったのかな?」と想像しつつ読む
このスタンスが現実的です。
もし、あなたの手元のコードに
なぜか StringBuffer が使われているループ
StringBuilder と StringBuffer が入り混じっている箇所
があれば、その部分を貼ってみてください。
