「Stringを結合してはダメ、StringBufferを使いましょう。」
Javaプログラマなら一度は聞いたことがありそうなテーマです。その通りですので初心者は意味も解らなくても従うべきかもしれませんが、細かい話になるとほとんどのプログラマが別々の見解を持っているので混乱するばかりです。その根拠の多くは人から聞いたやネットで見たなどであやふやです。
環境によって変化するので絶対の数値では勿論ありませんが、気になるので試してみることにします。文字列結合は次回ということで、とりあえずはStringを使うときのコストを研究してみます。
検証に使ったコードは以下のとおりです。
public class LoadString { private static int testCount; public static final String PUBLIC_STRING = "ABC"; private static final String PRIVATE_STRING = "ABC"; private static OuterConstant outerConstant = new OuterConstant(); public static void test(int count) { testCount = count; TEST_END: for (int i = 0; ; i++) { long start = System.currentTimeMillis(); switch (i) { case 0: System.out.println("directString()"); directString(); break; case 1: System.out.println("loadPrivateString()"); loadPrivateString(); break; case 2: System.out.println("loadPublicString()"); loadPublicString(); break; case 3: System.out.println("loadOuterStaticString()"); loadOuterStaticString(); break; case 4: System.out.println("loadOuterInstanceString()"); loadOuterInstanceString(); break; case 5: System.out.println("newString()"); newString(); break; case 6: System.out.println("newStringWithLiteral()"); newStringWithLiteral(); break; default: break TEST_END; } long end = System.currentTimeMillis(); double result = (double)(end - start) / 1000; System.out.println(result + "秒"); } } protected static void directString() { for (int i = 0; i < testCount; i++) { String s = "ABC"; } } protected static void loadPrivateString() { for (int i = 0; i < testCount; i++) { String s = PRIVATE_STRING; } } protected static void loadPublicString() { for (int i = 0; i < testCount; i++) { String s = PUBLIC_STRING; } } protected static void loadOuterStaticString() { for (int i = 0; i < testCount; i++) { String s = OuterConstant.OUTER_STATIC_STRING; } } protected static void loadOuterInstanceString() { for (int i = 0; i < testCount; i++) { String s = outerConstant.outerInstanceString; } } protected static void newString() { for (int i = 0; i < testCount; i++) { String s = new String(); } } protected static void newStringWithLiteral() { for (int i = 0; i < testCount; i++) { String s = new String("ABC"); } } public static void main(String[] args) { LoadString.test(5000000); } }
public class OuterConstant { public static final String OUTER_STATIC_STRING = "ABC"; public final String outerInstanceString = "ABC"; }
では実際に見ていくことにいます。
directString()
メソッド内で直接リテラルを記述する場合です。クラスファイルに直接埋め込まれるため高速に処理してくれます。
loadPrivateString()
loadPublicString()
フィールド定数は直接リテラルを記述する場合と同じ結果が得られました。フィールド定数は置換定数なのでクラスファイルにコンパイル時に埋め込まれるので当たり前といえば当たり前の結果です。publicとprivateによる差はありませんでした。
loadOuterStaticString()
疑い深いので他のクラスの定数を呼び出してみましたが、これも当然同じ速度ですね。
loadOuterInstanceString()
これは実は結構気になっていたのですが、インスタンス変数のアクセスは呼び出しにコストがかかるのかという問題です。結果は上記のメソッドらと同等の速度で遜色はありませんでした。クラスファイルを除いてみるとしっかりリテラルが埋め込まれていました。しかもVMが1.5のときにはStringBuilderもお目見えしていたので、かなり最適化されている模様。
newString()
直接リテラル書くのとnew String()はちがうのか?という疑問です。よく「””で囲った文字列を書くと暗黙でStringオブジェクトがnewされている」という発言を聞きます。確かに、VMはリテラルをStringとして使える状態にはすると思いますが、挙動はかなり違うと思います。それを立証するように、このメソッドは他のメソッドの10倍近いコストがかかりました。
newStringWithLiteral()
最後にnew String()するときに引数でリテラルを渡すとどうか?というものです。これは意外な結果でした。当然、明示的にStringオブジェクトを生成しているのでnewString()の場合同様にコストがかかるのですが、こちらの方が僅かに速い。コンストラクタで生成する文字列があった方が速いのは腑に落ちないので、時間があるときに調べてみようかと。一つ言えるのは「Stringオブジェクトの生成と、コンストラクタ内のリテラル暗黙Stringオブジェクトの生成で2重にコストがかかる」というような論はないようです。
結果としては「new String()」しないならあまり気にする必要はないというところですかね。VMは1.4と1.5で試しましたが、特に違いはありませんでした。(比較なのでマシンスペックはいいかな)