---
title: "JavaのStringBuilderとは：使い方とStringBufferとの違い、+連結との使い分けをコードで解説"
url: "https://www.issoh.co.jp/tech/details/5282/"
published: 2025-02-12
updated: 2026-10-13
categories: ["Java"]
publisher: "株式会社一創"
---

# JavaのStringBuilderとは：使い方とStringBufferとの違い、+連結との使い分けをコードで解説

StringBuilderは、Javaで文字列を効率よく組み立てるためのクラスです。変更できないStringと違い、同じインスタンスのまま文字を追加・挿入できるため、ループでの連結やログ・CSVの生成で使われます。この記事では、append・insert・改行・repeatといった基本操作、StringやStringBufferとの違い、そして「いつ明示的に使うべきか」までを、公式のJavadocと実際に動くコードで整理します。記載の仕様は2026年10月時点のJava 25（LTS）のAPIドキュメントで確認したものです。

## まとめ：StringBuilderの要点と、明示して使うべき場面の見分け方

- 役割：可変（mutable）な文字列バッファ。Stringのように変更のたびに新しいオブジェクトを作らない。
- 基本操作：末尾追加は`append`、途中挿入は`insert`、改行は`System.lineSeparator()`か`"\n"`、同じ文字の繰り返しはJava 21以降の`repeat`。`append`は戻り値が自身なのでメソッドチェーンできる。
- StringBufferとの違い：StringBufferは同期（スレッドセーフ）で、StringBuilderは非同期。単一スレッドならStringBuilderが速い。
- 使うべき場面：`+`を1つの式で書くだけの単純連結は、Java 9以降のコンパイラが効率的な連結処理に変換するため不要。ループ内や条件分岐で文字列を積み上げるときに明示する価値がある。
- 落とし穴：`equals`は中身でなく参照を比べる。中身の比較は`compareTo`（Java 11以降）か`toString()`同士で行う。

以下、仕組み・操作・判断基準の順に見ていきます。

## StringBuilderの仕組みと、不変のStringで連結を重ねると遅くなる理由

StringBuilderとは、Javaで可変な文字列を組み立てるためのクラスです。なぜこれが必要になるのかは、Stringの性質から説明できます。Javaの`String`は不変（immutable）です。`str += "x"`のように見える変更も、内部では毎回新しいStringインスタンスが生成されています。短い処理なら問題になりませんが、何千回も連結する場合は、生成と破棄の積み重なりによるメモリ消費と処理時間の増加が問題です。捨てられた中間オブジェクトはヒープに溜まり、[Javaのガベージコレクション（GC）の仕組みと種類](/tech/details/3084/)で解説している回収処理の負担として跳ね返ってきます。

StringBuilderは内部に文字の配列（バッファ）を1つ持ち、それを書き換えながら文字列を伸ばします。新しいオブジェクトを作らないため、繰り返しの組み立てに向きます。最後に`toString()`を呼んだ時点で確定したStringを取り出す流れです。[StringBuilderのJavadoc（Java 25）](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/StringBuilder.html)には「Since: 1.5」とあり、J2SE 5.0で追加されたクラスだとわかります。同じページには、単一スレッドで使うバッファではStringBufferより優先して使うよう推奨する記述もあります。

```
StringBuilder sb = new StringBuilder();
sb.append("Hello, ").append("World!");
String result = sb.toString();  // "Hello, World!"
```

内部の文字配列は、Java 9で入った[JEP 254（Compact Strings）](https://openjdk.org/jeps/254)以降、中身がLatin-1の範囲に収まる間は1文字1バイトで保持され、日本語などが入るとUTF-16へ切り替わります。利用者がこの違いを意識する必要はありませんが、英数字中心の組み立てではメモリ消費が小さく済みます。

## String・StringBuilder・StringBufferの可変性と同期の2軸比較

この3つは「文字列を扱う」点で混同されがちですが、可変性とスレッド安全性で役割が分かれます。検索でも「stringbuilder stringbuffer 違い」「string stringbuilder 違い」がよく調べられる論点です。

| クラス           | 可変性 | スレッドセーフ | 速度         | 主な用途            |
| ------------- | --- | ------- | ---------- | --------------- |
| String        | 不変  | 不変ゆえ安全  | 連結の繰り返しは低速 | 変更しない文字列        |
| StringBuilder | 可変  | なし      | 速い         | 単一スレッドでの組み立て    |
| StringBuffer  | 可変  | あり（同期）  | 同期の分だけ遅い   | 複数スレッドで共有するバッファ |

StringBuilderとStringBufferはAPIがほぼ同じで、どちらも内部実装（package-privateの`AbstractStringBuilder`）を共有しています。違いは、[StringBufferのJavadoc](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/StringBuffer.html)にあるとおり、各メソッドが`synchronized`で同期される点です。同期にはコストがあるため、1つのスレッドからしか触らないバッファならStringBuilderを選びます。複数スレッドが同じインスタンスへ同時に書き込む構成のときだけStringBufferを検討してください。なお、スレッドごとにバッファを分けて持てるなら、その場合もStringBuilderで十分です。

仮想スレッドを使う構成では、Java 21〜23で`synchronized`の中でブロックすると仮想スレッドがキャリアスレッドに固定（pinning）される制約がありました。これは[JEP 491](https://openjdk.org/jeps/491)によりJava 24で解消されています。StringBufferの同期は短時間で終わるため実害は小さいものの、仮想スレッドとの組み合わせは[Java Virtual Thread（仮想スレッド）の作り方と落とし穴](/tech/details/3209/)もあわせて確認しておくと安心です。

## StringBuilderの使い方をコードで確認する：生成から取り出しまでの基本操作

### 初期容量16文字の既定値と、容量を指定して再確保を減らす書き方

引数なしのコンストラクタは、初期容量16文字のバッファを確保します。保持する文字列が確保済みの容量を超えたときは、内部配列の再確保が必要です。Javadocの`ensureCapacity`の説明では、新しい容量は「要求された最小容量」と「旧容量の2倍＋2」の大きいほうと定められています。最終的な長さの見当がつくなら、最初から容量を指定すると再確保とコピーの回数を減らせます。文字列を渡して初期化することもでき、その場合の初期容量は「文字列の長さ＋16」です。

```
StringBuilder sb1 = new StringBuilder();         // 初期容量16文字
StringBuilder sb2 = new StringBuilder(1024);     // 大きめに確保し再確保を抑える
StringBuilder sb3 = new StringBuilder("接頭辞"); // 容量は 3 + 16 = 19
System.out.println(sb1.capacity()); // 16
System.out.println(sb3.capacity()); // 19
```

### appendでの末尾追加とメソッドチェーン、nullが4文字のnullになる挙動

`append`は引数を末尾に追加します。文字列だけでなく`int`・`boolean`・任意のオブジェクトも受け取り、オブジェクトの場合は`String.valueOf`と同じ変換結果が連結されます。戻り値は自分自身です。そのため、同じインスタンスへの追加操作を続けて`.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"`を連結します。CSVやログに意図せず`"null"`が混ざる原因になりやすいので、変数がnullになり得る箇所では`Objects.toString(s, "")`のように既定値を決めてから追加してください。

### 改行の追加は「\\n」とSystem.lineSeparator()を出力先で使い分け

改行は`append("\n")`でLF（ラインフィード）を入れられます。Windowsのテキストとして開く前提なら`"\r\n"`（CRLF）、実行環境の改行コードに合わせたいなら`System.lineSeparator()`、と用途で使い分けます。ファイル形式で改行コードが決まっている場合（RFC 4180のCSVはCRLF、多くの設定ファイルはLF）は、実行環境に依存させず固定の文字を書くほうが安全です。

```
StringBuilder sb = new StringBuilder();
sb.append("1行目").append(System.lineSeparator()); // 実行環境の改行
sb.append("2行目").append('\n');                    // 常に LF
```

### insertで指定位置へ挿入する方法と、範囲外で起きる例外の防ぎ方

`insert`は第1引数のインデックス位置に第2引数を割り込ませます。末尾に足す`append`と違い、途中へ差し込めるのが特徴です。指定位置が0未満か現在の長さを超えると`StringIndexOutOfBoundsException`が発生するため、可変長のデータを扱うときは`0 <= index && index <= sb.length()`を確かめてから呼びます。

```
StringBuilder sb = new StringBuilder("Hello World");
sb.insert(5, " Java");  // "Hello Java World"
sb.insert(0, '[');      // 先頭への挿入も可
sb.insert(100, "x");    // 長さを超える位置 → StringIndexOutOfBoundsException
```

### delete・replace・reverse・setLengthによる内容変更

そのほかよく使うメソッドを挙げます。範囲削除の`delete`、1文字削除の`deleteCharAt`、範囲置換の`replace`、反転の`reverse`、現在の文字数を返す`length`、長さを切り詰める`setLength`、確定したStringを取り出す`toString`です。`length`と`toString`以外は同じインスタンスを破壊的に変更します。`reverse`はサロゲートペア（絵文字など）を1文字として扱うため、反転で文字が壊れることはありません。各メソッドの引数や戻り値の正確な仕様はJavadocで確認でき、Javadocそのものの読み方と書き方は[Javadocの書き方（コメント・タグの記述例と生成方法）](/tech/details/3290/)で解説しています。

```
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
sb.setLength(0);        // 中身を空にする（容量は保持）
```

### Java 21で追加されたrepeatで、同じ文字列を指定回数だけ追加する

Java 21では`repeat(int codePoint, int count)`と`repeat(CharSequence cs, int count)`が追加されました（Javadocの表記は「Since: 21」）。区切り線やインデント、固定長のパディングをループなしで書けます。`count`が負の値なら`IllegalArgumentException`になります。Java 17以前を対象にするコードでは使えないため、ビルドの`--release`指定とあわせて判断してください。

```
StringBuilder sb = new StringBuilder();
sb.repeat('-', 10);          // "----------"
sb.append('\n').repeat("ab", 3); // 改行のあとに "ababab"
```

## そのまま動かせるサンプル：CSV生成とバッファの再利用を1ファイルで試す

ここまでの操作を1つのファイルにまとめました。Java 11以降は[JEP 330](https://openjdk.org/jeps/330)により、ソースファイルをコンパイルせず`java StringBuilderDemo.java`で直接実行できます。`repeat`を含むためJava 21以降で動かしてください。

```
import java.util.List;

public class StringBuilderDemo {
    public static void main(String[] args) {
        List<String[]> rows = List.of(
            new String[] {"id", "name", "qty"},
            new String[] {"1", "りんご", "3"},
            new String[] {"2", "みかん", "12"});

        // 1. CSVを組み立てる（RFC 4180に合わせて改行はCRLF）
        StringBuilder sb = new StringBuilder(64);
        for (String[] row : rows) {
            sb.append(String.join(",", row)).append("\r\n");
        }
        System.out.print(sb);

        // 2. setLength(0)で空にし、同じバッファを再利用する
        sb.setLength(0);
        sb.append("total=").append(15);
        sb.insert(0, '[').append(']');
        System.out.println(sb);                 // [total=15]

        // 3. Java 21以降のrepeat
        sb.setLength(0);
        System.out.println(sb.repeat('-', 10)); // ----------

        // 4. equalsは参照比較、中身の比較はcompareToかtoString
        StringBuilder a = new StringBuilder("java");
        StringBuilder b = new StringBuilder("java");
        System.out.println(a.equals(b));                       // false
        System.out.println(a.compareTo(b) == 0);               // true
        System.out.println(a.toString().equals(b.toString())); // true
    }
}
```

実行すると、3行のCSV、`[total=15]`、10個のハイフン、`false`・`true`・`true`の順に出力されます。`System.out.print(sb)`のようにStringBuilderをそのまま渡せるのは、`print(Object)`が内部で`String.valueOf`を呼ぶためです。

## +連結とStringBuilderの使い分け：Java 9以降のコンパイラの挙動から判断

「連結は必ずStringBuilderを使う」という説明をよく見かけますが、Java 9以降ではそのまま当てはまりません。[Javaマイグレーションの進め方（JDK 8から21／25への手順）](/tech/details/1314/)で扱う版の更新の中で、連結処理の仕組みが変わっているためです。

### 単純な+連結はJEP 280のinvokedynamicで処理され、手書きは不要

Java 9で導入された[JEP 280（Indify String Concatenation）](https://openjdk.org/jeps/280)により、`a + b + c`のような`+`連結を、`javac`は`invokedynamic`経由で[StringConcatFactory](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/invoke/StringConcatFactory.html)を呼ぶコードへコンパイルします。Java 8までのように裏でStringBuilderの呼び出し列へ展開するのではなく、実行時のJDKが効率のよい連結の方法を選ぶ仕組みです。したがって、1つの式で完結する単純な連結を、わざわざStringBuilderへ手で書き換える必要はありません。むしろ手で書くと、JDKの更新で連結処理が改善されても恩恵を受けにくくなります。

ただし、この変換は`javac`の出力先が9以上のときだけ働きます。`--release 8`でコンパイルしたクラスは、新しいJDKで動かしても旧来のStringBuilder展開のままです。Java 8向けの成果物を流用し続けている現場では、再コンパイルまでが移行作業に含まれます。

### javapでバイトコードを見て、+連結がどう変換されたかを確かめる手順

自分の環境で変換結果を確かめるには、JDK付属の`javap`で逆アセンブルします。次のクラスをコンパイルして`javap -c`を実行すると、Java 9以降の出力先では`invokedynamic`の行に`makeConcatWithConstants`が現れ、`--release 8`では`StringBuilder.append`の呼び出しが並びます。

```
// Concat.java
public class Concat {
    static String join(String a, int b) { return a + ":" + b; }
}

// 端末で実行
javac Concat.java && javap -c Concat
javac --release 8 Concat.java && javap -c Concat
```

### ループ内で文字列を積み上げる処理こそStringBuilderを明示する場面

コンパイラが変換できるのは「1つの式」です。ループのように繰り返しのたびに連結が分かれる場合は、各回が独立した連結となり、反復ごとに中間のStringが生成されます。ここがStringBuilderを明示すべき本命です。

```
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i).append(',');
}
String csv = sb.toString();
```

この処理を`String csv = ""; csv += ...;`で書くと、毎回それまでの全文字をコピーした新しいStringが作られるため、処理量は件数のおおむね2乗で増えます。件数が増えるほど差が開くので、ループでの連結はStringBuilderを選んでください。条件分岐で文字列を継ぎ足していくケースも同様です。

### 区切り連結でのString.joinとCollectors.joiningの選択

区切り文字でつなぐだけなら、StringBuilderを書かずに済むことも多くあります。固定要素や配列の連結は`String.join`、コレクションやストリームの連結は`Collectors.joining`が読みやすく、[Javaラムダ式とStream APIの使いどころ](/tech/details/2598/)で扱う書き方とも相性の良い選択です。接頭辞と接尾辞を付けながら少しずつ足したいときは[StringJoiner](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/StringJoiner.html)も使えます。

```
String csv = String.join(",", "A", "B", "C");  // "A,B,C"
String joined = list.stream().collect(Collectors.joining(", ", "[", "]")); // "[x, y]"
```

つまり判断は単純です。単発の連結は`+`、区切り連結は`String.join`系、ループや条件で積み上げるならStringBuilder、と使い分けます。

## StringBuilderで詰まりやすい落とし穴：equals・スレッド共有・文字コード

### equalsの参照比較とcompareTo・toStringによる中身の比較

StringBuilderは`equals`と`hashCode`をオーバーライドしていません。同じ文字列を持つ2つのインスタンスでも`equals`は`false`を返し、`HashSet`のキーにしても中身で重複を判定できません。中身を比べるなら、Java 11で追加された`compareTo`が0かどうかを見るか、`toString()`で確定したString同士を比べます。Javadocにも明記されているのは、「自然順序付けがequalsと一致しない」という比較時の注意点です。文字列比較の基本は[Javaのコード例で学ぶ書き方の基本（文字列比較・命名規則）](/tech/details/2350/)で整理しています。

### 複数スレッドで1つのStringBuilderを共有したときに起きる文字列の破損

Javadocは「StringBuilderのインスタンスは複数スレッドでの使用に対して安全ではない」と明記しています。並列ストリームや複数の非同期タスクから同じStringBuilderへ`append`したときに起きるのは、文字の欠落や混在、まれに`ArrayIndexOutOfBoundsException`が発生する問題です。対処は3つあり、スレッドごとにStringBuilderを持って最後に結合する、ストリームなら`Collectors.joining`に任せる、どうしても共有するならStringBufferに替える、の順で検討します。

### バイト列への変換はStandardCharsets.UTF\_8で文字コードを明示する

StringBuilderで組み立てた文字列を、ファイル出力や通信のためにバイト列へ変換する場面では、文字コードを明示します。`getBytes()`を引数なしで呼ぶと既定の文字コードが使われ、その既定値はJavaの版で変わっています。[JEP 400](https://openjdk.org/jeps/400)によりJava 18以降は既定がUTF-8になりましたが、Java 17以前の日本語Windowsでは既定がMS932（Shift\_JIS系）でした。同じコードが版と環境で別のバイト列を出すため、`StandardCharsets.UTF_8`のように明示しておけば文字化けの原因を1つ減らせます。

```
StringBuilder sb = new StringBuilder("こんにちは");
byte[] utf8 = sb.toString().getBytes(StandardCharsets.UTF_8); // 15バイト
```

## StringBuilderのC#・.NET版とJava版の違いと検索時の見分け方

「stringbuilder」で検索すると、Javaの`java.lang.StringBuilder`と、C#／.NETの`System.Text.StringBuilder`が混在して表示されます。名前と目的（可変な文字列バッファ）は似ていますが、別言語の別クラスで、メソッド名や挙動は完全には一致しません。たとえば.NET版には改行付きで追加する`AppendLine`や書式付きの`AppendFormat`がありますが、Java版にはありません。この記事はJavaを対象にしています。C#で同じことをしたい場合は、[Microsoft LearnのSystem.Text.StringBuilderクラス](https://learn.microsoft.com/ja-jp/dotnet/api/system.text.stringbuilder)を参照してください。VB.NETやPythonでの「StringBuilder相当」を探している場合も、それぞれの標準ライブラリの仕様に従う必要があります。

## StringBuilderを明示する場面・使わない場面の判断と性能の測り方

ここまでの内容を判断の形にまとめます。StringBuilderを明示するのは、(1)ループや再帰で文字列を積み上げる、(2)条件分岐で部分ごとに継ぎ足す、(3)`insert`や`replace`で途中を書き換える、(4)`setLength(0)`で同じバッファを使い回したい、のいずれかに当てはまるときです。逆に、1つの式で終わる連結、区切り文字で並べるだけの連結、複数スレッドから同時に書き込む処理では使いません。前の2つは`+`と`String.join`系、最後はスレッドごとのバッファかStringBufferの領分です。

性能差を自分で確かめたい場合は、`System.nanoTime()`の差分ではなく、OpenJDKの[JMH（Java Microbenchmark Harness）](https://github.com/openjdk/jmh)で測ってください。JITのウォームアップや不要コード除去の影響を受けるため、単純なループ計測では差が出たり消えたりします。測定の前に、本番と同じ件数・同じ文字種でデータを用意することが判断を誤らないコツです。

既存のJavaシステムで、帳票やCSVの生成が遅い・大量データでメモリが溢れるといった症状が出ているなら、文字列処理だけでなく設計全体を見直す時期かもしれません。一創では[フルスクラッチ開発](https://www.issoh.co.jp/service/system/fullscratch/)として、Javaを含む業務システムの新規開発と作り直しを支援しています。メモリ不足の切り分けは[Javaヒープのメモリ構造とOutOfMemoryErrorの対処](/tech/details/3365/)も参考になります。

## よくある質問

StringBuilderについて、検索でよく調べられている疑問に答えます。

### StringBuilderとStringBufferの違いは何ですか？

APIはほぼ同じで、違いはスレッド安全性です。StringBufferは各メソッドが`synchronized`で同期されるためマルチスレッドで共有しても安全ですが、その分わずかに遅くなります。StringBuilderは同期しない代わりに単一スレッドでは高速です。1つのスレッドからしか操作しないなら、StringBuilderを選べば十分です。JavadocもStringBuilderを優先して使うよう推奨しています。

### StringBuilderで改行するにはどうしますか？

`append("\n")`でLF（ラインフィード）を追加できます。実行環境の改行コードに合わせたいなら`System.lineSeparator()`を`append`します。一方、CSV（RFC 4180ではCRLF）のようにファイル形式で改行コードが決まっている場合は、環境に依存させず`"\r\n"`などを固定で書くほうが安全です。出力先の仕様に合わせて使い分けてください。

### StringBuilderの初期容量（デフォルト）はいくつですか？

引数なしで生成した場合の初期容量は16文字です。文字列を渡して生成した場合の初期容量は、「その文字列の長さ＋16」という計算で決まる値です。容量を超えると内部配列が「旧容量の2倍＋2」以上に再確保されます。最終的な長さの目安が分かっているなら、`new StringBuilder(1024)`のように初期容量を指定しておくと、再確保の回数を減らせます。

### appendにnullを渡すとどうなりますか？

例外にはならず、文字列`"null"`（4文字）が末尾に追加されます。`append((String) null)`も`append((Object) null)`も同じ挙動です。意図せず`"null"`が混入する原因になりやすいので、値がnullになり得る箇所では事前に判定するか、`Objects.toString(値, "")`で既定値に置き換えてから追加してください。

### StringBuilderの中身を空にして再利用するにはどうしますか？

`sb.setLength(0)`を呼ぶと、確保済みの容量を保ったまま中身だけが空になります。ループの中で毎回`new StringBuilder()`するより再確保が減るため、同じ処理を何度も繰り返す場面で有効です。`sb.delete(0, sb.length())`でも同じ結果になります。ただし、一時的に巨大な文字列を入れたバッファは容量も大きいまま残るので、長く保持する場合は`trimToSize()`で縮めるか作り直してください。

## 関連記事

- [Javaマイグレーションの進め方｜JDK 8→21/25の手順とSpring Boot・Jakarta移行](/tech/details/1314/)：+連結の変換を含め、版を上げるときの手順を確認できます。
- [Javaのコード例で学ぶ書き方の基本｜文字列比較・命名規則・ループ・Streamの実務パターン](/tech/details/2350/)：文字列比較など日常の書き方の基本です。
- [Javaラムダ式の使いどころ｜Stream APIと組み合わせる場面・避けるべき場面](/tech/details/2598/)：Collectors.joiningを使う書き方の前提になります。
- [Javaのガベージコレクション（GC）とは｜仕組み・種類・ログ確認とチューニング手順](/tech/details/3084/)：中間オブジェクトが回収処理に与える影響を理解できます。
- [Java Virtual Thread（仮想スレッド）とProject Loomとは？作り方・仕組み・落とし穴を実務目線で解説](/tech/details/3209/)：同期処理と仮想スレッドの関係を確認できます。

---

出典: [JavaのStringBuilderとは：使い方とStringBufferとの違い、+連結との使い分けをコードで解説](<https://www.issoh.co.jp/tech/details/5282/>)（株式会社一創）
