【Java】Java StringBuilderの使い方:文字列連結を効率化する方法

Java

こんにちは、かつコーチです。

前回はStringの基本を解説しましたが、大量の文字列を繰り返し連結する処理ではString+演算子は非効率です。
この記事では、そうした場面で使うStringBuilder(文字列を効率よく組み立てるためのクラス)の使い方と、なぜ効率が良いのかという理由まで踏み込んで解説します。

StringBuilderとは?

なぜStringの+連結は非効率なのか

Stringはイミュータブル(一度作成すると内容を変更できない性質)なオブジェクトです。
+で文字列を連結するたびに、実は新しいStringオブジェクトが毎回生成されています。

String result = "";
for (int i = 0; i < 5; i++) {
    result = result + i; // 毎回新しいStringオブジェクトが作られる
}

このコードは一見問題なく動きますが、ループ回数が数千・数万回になると、その都度新しいオブジェクトを生成してはすぐに捨てる、という無駄な処理が積み重なり、パフォーマンスが大きく低下します。

StringBuilderは可変な文字列バッファ

StringBuilderは、内部にバッファ(可変な文字の配列のようなもの)を持ち、そこに直接文字を追加していくクラスです。
新しいオブジェクトを都度生成しないため、大量の連結処理でもメモリと処理時間を節約できます。

基本の書き方:StringBuilderの使い方

append・toStringの基本

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < 5; i++) {
            sb.append(i);
            sb.append(",");
        }

        String result = sb.toString();
        System.out.println(result); // 0,1,2,3,4,
    }
}

appendメソッドで文字列や数値をどんどん追加し、最後にtoString()で通常のStringに変換します。
appendは数値・boolean・char・他のオブジェクトなど、さまざまな型を受け取れるオーバーロードが用意されています。

insert・delete・reverseで柔軟に操作する

StringBuilder sb = new StringBuilder("Hello World");

sb.insert(5, ","); // 指定位置に挿入
System.out.println(sb); // Hello, World

sb.delete(0, 6); // 0〜5文字目を削除
System.out.println(sb); // World

sb.reverse(); // 文字列を反転
System.out.println(sb); // dlroW

Stringにはないinsert(途中への挿入)、delete(範囲削除)、reverse(反転)といったメソッドが使えるのも、可変であるStringBuilderならではの特徴です。

よくあるつまずきポイント・エラー対処

大量ループでStringを使い続けてパフォーマンス劣化に気づかなかった一次情報

私が実際に、CSVのような大きなテキストデータを1万行分組み立てる処理を書いたときの話です。

❌ Before
String csv = "";
for (int i = 0; i < 10000; i++) {
    csv = csv + i + "," + (i * 2) + "\n";
}

このコードは動作はするものの、実行にかなりの時間がかかりました。
1万回のループのたびに、それまで組み立てた文字列全体をコピーした新しいStringオブジェクトが生成されるため、処理が進むほど1回あたりのコピー量が増えていきます。
エラーにはならないため気づきにくいのですが、行数が増えるほど処理時間が目に見えて悪化する典型的なパターンでした。

✅ After
StringBuilder csvBuilder = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    csvBuilder.append(i).append(",").append(i * 2).append("\n");
}
String csv = csvBuilder.toString();

StringBuilderに書き換えたところ、体感できるほど処理時間が短縮されました。
appendは自分自身(StringBuilderのインスタンス)を返すため、.append().append()のようにメソッドチェーン(メソッドを連続して呼び出す書き方)でつなげて書けます。

StringBuilderをそのまま比較してハマるミス

❌ Before
StringBuilder sb1 = new StringBuilder("かつコーチ");
StringBuilder sb2 = new StringBuilder("かつコーチ");
System.out.println(sb1.equals(sb2)); // false

StringBuilderはequalsをオーバーライドしていないため、Stringのように中身を比較してくれません。
Objectクラスのequalsがそのまま使われ、結局==と同じ「参照が同じかどうか」の比較になってしまいます。

✅ After
StringBuilder sb1 = new StringBuilder("かつコーチ");
StringBuilder sb2 = new StringBuilder("かつコーチ");
System.out.println(sb1.toString().equals(sb2.toString())); // true

StringBuilder同士の中身を比較したい場合は、toString()でStringに変換してからequalsを使う必要があります。

応用・一歩先の使い方

StringBuilderとStringJoinerの使い分け

import java.util.StringJoiner;

StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.add("Java");
joiner.add("Python");
joiner.add("PHP");

System.out.println(joiner.toString()); // [Java, Python, PHP]

区切り文字・前後の括弧付きでリストのような文字列を組み立てたい場合は、StringJoinerの方がコードが簡潔になります。
単純な連結ならStringBuilder、区切り文字付きのリスト表現ならStringJoiner、と目的に応じて使い分けるとよいでしょう。

まとめ

この記事のポイント

  • Stringはイミュータブルなため、+演算子での大量連結はパフォーマンスが悪化しやすい
  • StringBuilderは可変なバッファを持ち、append中心の操作でメモリ効率よく文字列を組み立てられる
  • StringBuilderのequalsは中身を比較しないため、比較するにはtoString()が必要
  • 区切り文字付きの連結にはStringJoinerも選択肢に入れる

次に読むべき記事

  • Stringの基本:作成・比較・連結
  • メソッドの定義と引数の基本

タグ: Java, 中級者向け, 基本文法

タイトルとURLをコピーしました