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

きどたかのブログ

いつか誰かがこのブログからトラブルを解決しますように。

クラスファイルで見るJavaの文字列連結

「文字列連結をする時は+で繋げるな」ってコーディング規約に書いてあると思う。

それはコンパイラの改善でとっくの昔にStringBuilderに変わってる。

そんなことは少しJavaを知ってる人なら誰でも知っているさ。

 

今日書くのはそれ意外の部分についての復習。 

 

文字列リテラルの文字列連結

    	String s = "a" + "b" + "c";

 メソッド内で書いたこのコードがどうなるか知ってますか?

こうなります。

    0  ldc <String "abc"> [16]
    2  astore_1 [s]

 StringBuilderは使われません。

 

ローカル変数の文字列連結

では少しローカル変数におとしてみましょうか。

	String a = "a";
	String b = "b";
	String c = "c";
	String s = a + b + c;

こうなります。

     0  ldc <String "a"> [16]
     2  astore_1 [a]
     3  ldc <String "b"> [18]
     5  astore_2 [b]
     6  ldc <String "c"> [20]
     8  astore_3 [c]
     9  new java.lang.StringBuilder [22]
    12  dup
    13  aload_1 [a]
    14  invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [24]
    17  invokespecial java.lang.StringBuilder(java.lang.String) [30]
    20  aload_2 [b]
    21  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [33]
    24  aload_3 [c]
    25  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [33]
    28  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [37]
    31  astore 4 [s]

さすがにStringBuilderが使われますね。

一番初めの文字列はvalueOf(Object)が使われ、

参照がnullであった場合の対策がほどこされています。

コーディング規約どおりにやれば、この無駄な部分が削減できます。

とは言っても性能的には大した話ではない。

StringのvalueOf(Object)にString渡したら、null判定やStringの判定をして、同じインスタンスが返ってくると思うよ。

 

finalローカル変数の連結

では次に、さっきのローカル変数にfinalをつけるとどうなるか?

	final String a = "a";
	final String b = "b";
	final String c = "c";
	String s = a + b + c;

こうなります。 

     0  ldc <String "a"> [16]
     2  astore_1 [a]
     3  ldc <String "b"> [18]
     5  astore_2 [b]
     6  ldc <String "c"> [20]
     8  astore_3 [c]
     9  ldc <String "abc"> [22]
    11  astore 4 [s]

面白いことに、これにはStringBuilderは使われません。

finalを付ければなんでもこうなるわけではありません。

ちょっと小賢しいことをしてみましょう。

	final String a;
	final String b;
	final String c;
	a = "a";
	b = "b";
	c = "c";
	String s = a + b + c;

こうなります。

     0  ldc <String "a"> [8]
     2  astore_1 [a]
     3  ldc <String "b"> [11]
     5  astore_2 [b]
     6  ldc <String "c"> [14]
     8  astore_3 [c]
     9  new java.lang.StringBuilder [27]
    12  dup
    13  aload_1 [a]
    14  invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [29]
    17  invokespecial java.lang.StringBuilder(java.lang.String) [35]
    20  aload_2 [b]
    21  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [38]
    24  aload_3 [c]
    25  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [38]
    28  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [42]
    31  astore 4 [s]

まあ予想通りの結果ですね。

ひとつ付け加えるとすれば、未初期化の変数を宣言しただけでは

なにもインストラクションはおこらないということくらいです。

一般的な必ずnull初期化しましょうというコーディングは、

Javaにおいては無駄なインストラクションであることがあります。

あれは未初期化の変数がnull初期化されてることを期待するなというが本来の目的です。 

条件分岐ののちに必ず初期化する類の変数はnull初期化しなくてもいい。

しかしながら、可読性のこともあるため、未初期化の変数宣言のすぐ後ろにおいて初期化をすることが前提になるでしょう。

自分の場合はそういう変数にはfinal付けることも考えます。

 

static finalな変数の文字列連結その1

では一般的なstatic finalな変数で試しましょう。

	String s = A + B + C;

 こうなります。

    0  ldc <String "abc"> [27]
    2  astore_1 [s]

getstaticではなく、このケースではldcが呼ばれます。

 

static finalな変数の文字列連結その2

ちょっとCだけfinalを外してみましょうか。

     0  new java.lang.StringBuilder [29]
     3  dup
     4  ldc <String "ab"> [31]
     6  invokespecial java.lang.StringBuilder(java.lang.String) [33]
     9  getstatic Boxing.c : java.lang.String [18]
    12  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [36]
    15  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [40]
    18  astore_1 [s]

先頭の2文字の方は依然としてldcで読まれます。

面倒臭いからjavapしてませんが、

これはコンスタントプールの31番目に"ab"が登録されてることも意味してます。

つまり"a"や"b"だけでなく、"ab"も別物としてコンスタントプールにいます。

これまででてきたldcも全部そういうことです。

"c"という文字もコンスタントプールにいるはずですが、

ldcではなくgetstaticで取ってきてます。

 

finalなインスタンス変数の文字列連結

賢い人はもうお気付きだと思いますが、staticである必要性はありません。

インスタンス変数であってもfinalがついてればldcで読むのは可能です。

 

ldcが使われるのには少し条件があると思います。

ldc自体はコンスタントプールから値を取ってくる命令なわけで、

finalが付いてる必要性とかはldcそのものにはない。

コンパイラはなぜldcを使ったのでしょうか。

compile-time constant expressionだったかな?

4.12.4 final Variables

15.28 Constant Expression

 

重要だからここだけはfinal Variablesから引用しておく。

We call a variable, of primitive type or type String, that is final and initialized with a compile-time constant expression (§15.28) a constant variable.

つまりfinalでかつcompile-time constant expressionで初期化された変数が「定数」。

昔も書いたけど「定数」はstaticが付いてる必要性はない。

しかし、そのフィールドにConstantValue attributeがつくにはstaticじゃないといけない。そこが理解に苦しむところだ。間違ってるんじゃないかな。

4.7.2 The ConstantValue Attribute

Eclipseコンパイラの結果だとnon-staticでもConstantValue attributeが付いてるようなんだが、

これは無視されなければならない。

なんなんだろうね、この矛盾を含んだような物言いは。

Oracleコンパイラでも同じ結果だった。 謎だ。

 

あと「定数」のみがコンスタントプールにあるわけではない。誤解しないように。