読者です 読者をやめる 読者になる 読者になる

文字列の結合

実はこの前 (正確には今日) まで、
Javaの文字列連結パフォーマンスクイズ
Javaの文字列連結パフォーマンスクイズ解答編
を信じていたんだけど・・・
やっぱり自分で調べてみるもんですね。

文字列連結演算子 + (§15.18.1) の演算結果がコンパイル時の定数 (§15.28) とならない場合, 新たな String オブジェクトが暗黙のうちに生成される。

Java 言語仕様 第 3 版 4.3.3 クラス String (P.47)

コンパイル時の定数式 (constant expression) とは, 以下のみを使用することによって構成されたプリミティブ型や String の値を表す式であり, 途中終了することはない。

  • プリミティブ型のリテラルや String 型 (§3.10.5) のリテラル
  • プリミティブ型へのキャストや String 型へのキャスト
  • 加減演算子 +, -
  • 定数変数 (§4.12.4) を参照する単純名
  • 定数変数 (§4.12.4) を参照する TypeName.Identifier (型名 . 識別子) 形式の限定名



String 型のコンパイル時定数は, 固有のインスタンスを共有するため, メソッド String.intern を用いて常に「インターン」される。

定数式の例:

true
(short)(1*2*3*4*5*6)
Integer.MAX_VALUE / 2
2.0 * Math.PI
"The integer " + Long.MAX_VALUE + " is mighty big."
Java 言語仕様 第 3 版 15.28 定数式 (P.459〜460)

final であり, コンパイル時の定数式 (§15.28) によって初期化されたプリミティブ型や String 型の変数を定数変数 (constant variable) と呼ぶ。

Java 言語仕様 第 3 版 4.12.4 final 変数 (P.67)

てことで、

System.out.println("Hello, " + "world!"); // => Hello, world!

このプログラムは、コンパイル時に連結されて、以下のプログラムに変換される。

System.out.println("Hello, world!"); // => Hello, world!

同様に、整数リテラル等の場合にも、コンパイル時に連結される。

System.out.println("3 + 3 = " + 6); // => 3 + 3 = 6
// System.out.println("3 + 3 = 6");に変換される

また、定数変数も展開される。

public static final String WORLD = "World!";
public static void main(String[] args) {
    final String HELLO = "Hello, ";
    System.out.println(HELLO + WORLD); // => Hello, world!
    // System.out.println("Hello, world!");に変換される
}

public static final int SIX = 6;
public void m() {
    System.out.println("3 + 3 = " + SIX); // => 3 + 3 = 6
    // System.out.println("3 + 3 = 6");に変換される
}


また、上のブログエントリ中でも引用されているように、

連続して文字列連結を行う際におけるパフォーマンスの向上を目的として, クラス StringBuffer や同種の手法を採用し, 式の評価中に生成される一時的な String オブジェクト数を削減することが Java コンパイラに対して許されている。
プリミティブ型の場合, プリミティブ型を文字列に直接変換することによって, ラッパ・オブジェクトの生成を回避するような最適化も実装に対して許されている。

Java 言語仕様 第 3 版 15.18.1.2 文字列連結の最適化 (P.435)

とあるので、

static void printAll(String...strs) {
    String all = "";
    for (String str : strs)
        all += str;
    System.out.println(all);
}

public static void main(String[] args) {
    printAll("Hello, ", "world");
}

は、

static void printAll(String...strs) {
    String all = "";
    for (String str : strs) {
        StringBuilder buf = new StringBuilder(all.length() + str.length());
        all = buf.append(all).append(str).toString();
    }
    System.out.println(all);
}

のように最適化される可能性がある
引用中で、許されているという表現になっているとおり、このような最適化を行わないような実装も許されている。


ただし、上のようなメソッドの場合、もちろん自前で StringBuilder を用意する方が望ましい。

static void printAll(String...strs) {
    StringBuilder buf = new StringBuilder();
    for (String str : strs) {
        buf.append(str);
    }
    System.out.println(buf);
}