JavaのStringBuilderとは|使い方・StringBufferとの違いと使い分け
StringBuilderは、Javaで文字列を効率よく組み立てるためのクラスです。変更できないStringと違い、同じインスタンスのまま文字を追加・挿入できるため、ループでの連結やログ・CSVの生成で使われます。この記事では、append・insert・改行といった基本操作、StringやStringBufferとの違い、そして「いつ明示的に使うべきか」までを、実際に動くコードで整理します。
目次
まとめ:StringBuilderの要点
- 役割:可変(mutable)な文字列バッファ。Stringのように変更のたびに新しいオブジェクトを作らない。
- 基本操作:末尾追加は
append、途中挿入はinsert、改行はSystem.lineSeparator()か"\n"。appendは戻り値が自身なのでメソッドチェーンできる。 - StringBufferとの違い:StringBufferは同期(スレッドセーフ)で、StringBuilderは非同期。単一スレッドならStringBuilderが速い。
- 使うべき場面:
+を1回書くだけの単純連結はJava 9以降コンパイラが最適化するため不要。ループ内や条件分岐で文字列を積み上げるときに明示する価値がある。
以下、それぞれの操作と判断基準を順に見ていきます。
StringBuilderの基本概念と、Stringを使わない理由
StringBuilderとは、Javaで可変な文字列を組み立てるためのクラスです。なぜこれが必要になるのかは、Stringの性質から説明できます。JavaのStringは不変(immutable)です。str += "x"のように見える変更も、内部では毎回新しいStringインスタンスが生成されています。短い処理なら問題になりませんが、何千回も連結すると生成と破棄が積み重なり、メモリと処理時間を消費します。
StringBuilderは内部にcharの配列(バッファ)を1つ持ち、それを書き換えながら文字列を伸ばします。新しいオブジェクトを作らないため、繰り返しの組み立てに向きます。最後にtoString()を呼んだ時点で確定したStringを取り出します。Java 5(J2SE 5.0)で追加されたクラスで、APIはjava.lang.StringBuilderとして標準ライブラリに含まれます。
StringBuilder sb = new StringBuilder();
sb.append("Hello, ").append("World!");
String result = sb.toString(); // "Hello, World!"
String・StringBuilder・StringBufferの違いと使い分け
この3つは「文字列を扱う」点で混同されがちですが、可変性とスレッド安全性で役割が分かれます。検索でも「stringbuilder stringbuffer 違い」「string stringbuilder 違い」がよく調べられる論点です。
| クラス | 可変性 | スレッドセーフ | 速度 | 主な用途 |
|---|---|---|---|---|
| String | 不変 | 不変ゆえ安全 | ―(連結の繰り返しは低速) | 変更しない文字列 |
| StringBuilder | 可変 | なし | 速い | 単一スレッドでの組み立て |
| StringBuffer | 可変 | あり(同期) | やや遅い | 複数スレッドで共有するバッファ |
StringBuilderとStringBufferはAPIがほぼ同じで、どちらも内部実装(package-privateのAbstractStringBuilder)を共有しています。違いは、StringBufferの各メソッドがsynchronizedで同期される点だけです。同期にはコストがあるため、1つのスレッドからしか触らないバッファならStringBuilderを選びます。複数スレッドが同じインスタンスへ同時に書き込む構成のときだけStringBufferを検討してください。なお、スレッドごとにバッファを分けて持てるなら、その場合もStringBuilderで十分です。
StringBuilderの基本的な使い方
生成と初期化、初期容量の指定
引数なしのコンストラクタは、初期容量16文字のバッファを確保します。容量を超えると内部配列が再確保(おおむね倍増)されるため、最終的な長さの見当がつくなら、最初から容量を指定すると再確保を減らせます。文字列を渡して初期化することもできます。
StringBuilder sb1 = new StringBuilder(); // 初期容量16文字
StringBuilder sb2 = new StringBuilder(1024); // 大きめに確保し再確保を抑える
StringBuilder sb3 = new StringBuilder("接頭辞"); // 文字列で初期化
append:末尾への追加とメソッドチェーン
appendは引数を末尾に追加します。文字列だけでなくint・boolean・任意のオブジェクトも受け取り、オブジェクトの場合はtoString()の結果が連結されます。戻り値が自分自身なので、続けて.append(...)とつなげられます。
StringBuilder sb = new StringBuilder();
sb.append("数量:").append(100).append(true); // "数量:100true"
String s = null;
sb.append(s); // 例外ではなく "null" の4文字が追加される
注意したいのはnullの扱いです。append((String) null)はNullPointerExceptionにならず、文字列"null"を連結します。意図せず"null"が混ざる原因になりやすいので、変数がnullになり得る箇所では事前に判定してください。
改行の追加(\n と System.lineSeparator)
改行はappend("\n")でLF(ラインフィード)を入れられます。Windowsのテキストとして開く前提なら"\r\n"(CRLF)、出力環境が混在しうるなど移植性を重視するなら、その環境の改行コードを返すSystem.lineSeparator()、と用途で使い分けます。
StringBuilder sb = new StringBuilder();
sb.append("1行目").append(System.lineSeparator());
sb.append("2行目").append("\n"); // 固定の LF。環境非依存なら lineSeparator()
insert:指定位置への挿入
insertは第1引数のインデックス位置に第2引数を割り込ませます。末尾に足すappendと違い、途中へ差し込めるのが特徴です。指定位置が現在の長さを超えるとStringIndexOutOfBoundsExceptionが発生するため、可変長のデータを扱うときはインデックスの妥当性を確認します。
StringBuilder sb = new StringBuilder("Hello World");
sb.insert(5, " Java"); // "Hello Java World"
sb.insert(100, "x"); // 長さを超える位置 → StringIndexOutOfBoundsException
主要メソッド:delete・replace・reverse・toString・length
そのほかよく使うメソッドを挙げます。範囲削除のdelete、範囲置換のreplace、反転のreverse、現在の文字数を返すlength、確定したStringを取り出すtoStringです。いずれも対象は同じインスタンスで、toString以外は破壊的に変更します。各メソッドの引数や戻り値の正確な仕様は公式のAPIドキュメントで確認でき、その生成の仕組みはJavadocの概要と重要性で解説しています。
StringBuilder sb = new StringBuilder("abcdef");
sb.delete(0, 2); // "cdef"(0以上2未満を削除)
sb.replace(0, 1, "C"); // "Cdef"
sb.reverse(); // "fedC"
int len = sb.length(); // 4
文字列連結にStringBuilderを使うべきか(判断基準)
「連結は必ずStringBuilderを使う」という説明をよく見かけますが、Java 9以降ではそのまま当てはまりません。最新のLTSに至るJavaのバージョン更新の中で連結処理の仕組みが変わっているためです。
単純な連結はコンパイラが最適化する(明示は不要)
Java 9で導入されたJEP 280(Indify String Concatenation)により、a + b + cのような+連結を、javacはinvokedynamic経由でStringConcatFactoryを呼ぶコードへコンパイルします。かつてのように裏でStringBuilderへ自動変換するのではなく、実行時に効率的な連結処理が選ばれます(仕様はOpenJDKのJEP 280に記載されています)。したがって、1つの式で完結する単純な連結を、わざわざStringBuilderへ手で書き換える必要はありません。むしろ明示すると、この最適化の恩恵を受けにくくなります。
ループ内の積み上げこそStringBuilderの出番
コンパイラが最適化できるのは「1つの式」です。ループのように繰り返しのたびに連結が分かれる場合は、各回が独立した連結となり最適化が効きません。ここがStringBuilderを明示すべき本命です。
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i).append(',');
}
String csv = sb.toString();
この処理をString csv = ""; csv += ...;で書くと、反復ごとに中間のStringが生成されます。件数が増えるほど差が開くため、ループでの連結はStringBuilderを選んでください。条件分岐で文字列を継ぎ足していくケースも同様です。
String.join・Collectors.joiningで足りる場面
区切り文字でつなぐだけなら、StringBuilderを書かずに済むことも多くあります。固定要素の連結はString.join、コレクションやストリームの連結はCollectors.joiningが読みやすく、ラムダ式とStream APIを使った書き方とも相性が良い選択です(Collectors.joiningの利用にはjava.util.stream.Collectorsのインポートが必要です)。
String csv = String.join(",", "A", "B", "C"); // "A,B,C"
String joined = list.stream().collect(Collectors.joining(", "));
つまり判断は単純です。単発の連結は+、区切り連結はString.join系、ループや条件で積み上げるならStringBuilder、と使い分けます。
バイト列への変換とエンコーディングの指定
StringBuilderで組み立てた文字列を、ファイル出力や通信のためにバイト列へ変換する場面では、文字コードを明示します。Javaの文字列は内部的にUTF-16(Java 9以降は内容に応じてLatin-1かUTF-16を使うcompact strings)で保持されているため、外部とやり取りするUTF-8などへの変換はこの段階で行います。
StringBuilder sb = new StringBuilder("こんにちは");
byte[] utf8 = sb.toString().getBytes(StandardCharsets.UTF_8);
エンコーディングを省略するとプラットフォーム既定の文字コードが使われ、環境によって結果が変わります。StandardCharsets.UTF_8のように明示すれば、文字化けの原因を1つ減らせます。
C#・.NETのStringBuilderとの違い(同名異義)
「stringbuilder」で検索すると、Javaのjava.lang.StringBuilderと、C#/.NETのSystem.Text.StringBuilderが混在して表示されます。名前と目的(可変な文字列バッファ)は似ていますが、別言語の別クラスで、メソッド名や挙動は完全には一致しません。この記事はJavaを対象にしています。C#で同じことをしたい場合は、Microsoft LearnのSystem.Text.StringBuilderを参照してください。VB.NETやPythonでの「StringBuilder相当」を探している場合も、それぞれの標準ライブラリの仕様に従う必要があります。
よくある質問(FAQ)
StringBuilderとStringBufferの違いは何ですか?
APIはほぼ同じで、違いはスレッド安全性です。StringBufferは各メソッドがsynchronizedで同期されるためマルチスレッドで共有しても安全ですが、その分わずかに遅くなります。StringBuilderは同期しない代わりに単一スレッドでは高速です。1つのスレッドからしか操作しないなら、StringBuilderを選べば十分です。
StringBuilderで改行するにはどうしますか?
append("\n")でLF(ラインフィード)を追加できます。出力環境がWindowsとUnix系で混在する可能性があるなら、その環境の改行コードを返すSystem.lineSeparator()をappendするほうが移植性が高くなります。用途に応じて使い分けてください。
StringBuilderの初期容量(デフォルト)はいくつですか?
引数なしで生成した場合の初期容量は16文字です。容量を超えると内部配列が再確保されます。最終的な長さの目安が分かっているなら、new StringBuilder(1024)のように初期容量を指定しておくと、再確保の回数を減らせます。
appendにnullを渡すとどうなりますか?
例外にはならず、文字列"null"(4文字)が末尾に追加されます。append((String) null)もappend((Object) null)も同じ挙動です。意図せず"null"が混入する原因になりやすいので、値がnullになり得る箇所では事前に判定してから追加してください。
C#のStringBuilderとJavaのStringBuilderは同じですか?
名前と目的は似ていますが、別言語の別クラスです。JavaのものはJDKのjava.lang.StringBuilder、C#のものは.NETのSystem.Text.StringBuilderで、メソッド名や細かい挙動は一致しません。検索結果には両方が出るため、利用している言語に合った資料を選んでください。