1. 文字列の不変性: 味方か敵か?
Java の String クラスは不変(immutable)です。つまり、一度作成した文字列は変更できません。文字列を「変更」しているように見えても、例えば + や concat() で何かを足すたびに新しいオブジェクトが生成され、古いものはガベージコレクタに回収されるまでメモリに残ります。
例:
String s = "Hello";
s = s + " world!";
System.out.println(s); // Hello world!
見た目は文字列 s が変わったようですが、実際には新しい文字列 "Hello world!" が作られ、古い "Hello" はガベージコレクションされるまでメモリに残ります。こうした操作が多い(例えばループ内で)と、プログラムは遅くなり、余計なメモリを消費します。
積み木で塔を作っていて、新しいブロックを一つ足すたびに塔全体を組み直さなければならないとしたら非効率ですよね? 頻繁に変更がある場合の通常の String は、まさにそのように動作します。
2. StringBuilder: 高速な「文字列ビルダー」
StringBuilder クラス(java.lang パッケージ、インポート不要)は、文字列を効率よく組み立て・変更するためのツールです。これは可変(mutable)で、各操作ごとに新しいオブジェクトを作らずに、文字や部分文字列の追加・削除・挿入ができます。
たとえ話:
String がコンクリートの板だとすれば、StringBuilder は LEGO のようなブロック。全体を壊さずに部品を付け外しできます。
StringBuilder の作り方
StringBuilder sb = new StringBuilder(); // 空
StringBuilder sb2 = new StringBuilder("初期値");
主なメソッド
| メソッド | 説明 | 使用例 |
|---|---|---|
|
末尾に文字列・数値・文字などを追加 | |
|
指定位置に値を挿入 | |
|
start(含む)から end(含まない)までの文字を削除 | |
|
一部を別の内容に置換 | |
|
文字列を逆順にする | |
|
通常の String に変換 | |
|
指定した長さになるよう切り詰める/埋める | |
使用例
StringBuilder sb = new StringBuilder();
sb.append("Hello, ");
sb.append("world!");
System.out.println(sb); // Hello, world!
sb.insert(7, "Java "); // "Hello, " の後に "Java " を挿入
System.out.println(sb); // Hello, Java world!
sb.replace(8, 12, "another"); // "Java" を "another" に置換
System.out.println(sb); // Hello, another world!
sb.reverse();
System.out.println(sb); // !dlrow rehtona ,olleH
3. StringBuffer: マルチスレッド対応の兄貴分
StringBuilder と StringBuffer の違いは何ですか?
- StringBuilder — 高速だがスレッドセーフではない(非同期化)。
- StringBuffer — やや遅いがスレッドセーフ(同期化済み)。
アプリケーションが単一スレッドで動くなら StringBuilder(高速)を使いましょう。複数スレッドが同じ文字列を変更しうるなら StringBuffer を使います。
4. いつ String の代わりに StringBuilder を使うべきか?
シナリオ
- ループや大きなテキストの組み立てで、文字列の追加・削除・挿入が頻繁に発生する場合。
- 配列/リストからの文字列構築(CSV、HTML、レポートなど)。
- 多数の文字列操作を伴うテキストのパースや処理。
例: 配列から文字列を組み立てる
悪い例(String と + を使用):
String[] names = {"Ivan", "Pyotr", "Maria"};
String result = "";
for (int i = 0; i < names.length; i++)
{
result += names[i];
if (i < names.length - 1)
{
result += ", ";
}
}
System.out.println(result);
良い例(StringBuilder を使用):
String[] names = {"Ivan", "Pyotr", "Maria"};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.length; i++)
{
sb.append(names[i]);
if (i < names.length - 1)
{
sb.append(", ");
}
}
System.out.println(sb.toString());
違い: 前者は各ステップで新しい文字列が生成されます。後者は一つの StringBuilder オブジェクトを伸長していきます。
5. パフォーマンス比較: String 対 StringBuilder
// String を使う場合
long t1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 10000; i++)
{
s += i + " ";
}
long t2 = System.currentTimeMillis();
System.out.println("String: " + (t2 - t1) + " ms");
// StringBuilder を使う場合
t1 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.append(i).append(" ");
}
s = sb.toString();
t2 = System.currentTimeMillis();
System.out.println("StringBuilder: " + (t2 - t1) + " ms");
結論: 多数の連結がある場合、StringBuilder はほとんどのケースで桁違いに高速です。
6. 知っておきたいポイント
String のメソッドは使えますか?
StringBuilder にはそれ自身のメソッドがあります。文字列が必要なときは toString() を呼び出してください。
StringBuilder sb = new StringBuilder("Hello");
String s = sb.toString(); // これで s は通常の String
equals で StringBuilder を比較できますか?
注意: sb1.equals(sb2) は内容ではなく参照を比較します。次のように比較します:
if (sb1.toString().equals(sb2.toString()))
{
// 内容が一致
}
System.out.println に StringBuilder を渡せますか?
はい。System.out.println は自動的に toString() を呼び出します。
インデックスで文字を取得できますか?
はい。通常の文字列と同様に charAt(int index) を使います。
7. StringBuilder と StringBuffer でのよくあるミス
誤り1: 2 つの StringBuilder を equals や演算子 == で比較する。これらは参照比較であり、内容は比較しません。toString() で文字列にして比較しましょう。
誤り2: String が期待される場所(メソッドの戻り値、ログ出力、API への引き渡し)で toString() を呼び忘れる。
誤り3: 簡単な数回の連結にまで StringBuilder を使う。例えば "Hello, " + name のような書き方は読みやすく十分効率的です。
誤り4: ループ内で String を + で連結する。時間・メモリの両面で非効率です — StringBuilder を使いましょう。
誤り5: 必要もないのに StringBuffer と StringBuilder を混同する。複数スレッドからの共同変更がなければ StringBuilder を選びます。
誤り6: StringBuilder の setLength() で無分別に切り詰める。新しい長さの妥当性を確認しましょう。以降のデータは失われます。
GO TO FULL VERSION