文字列結合の+演算子 (1)
StringBuffer sql = new StringBuffer(); sql.append("SELECT"); sql.append(" hoge1, hoge2, hoge3, hoge4"); sql.append("FROM"); sql.append(" foo AS a"); sql.append("LEFT OUTER JOIN bar AS b"); sql.append("ON a.hoge1 = b.fuga1"); ... // 以下、長大なSQL文が続く
"+"演算子による文字列結合は遅いから……といって、ソースコードのSQL文を延々上記みたいな感じで一生懸命書いている御仁がいらっしゃいました *1。しかし、もう+演算子はStringBufferとかStringBuilderにjavacが自動変換してしまうようになっていると耳にしました。であるならば、上記のようなコードは、労力がかかるだけであんまり意味が無いどころか、StringBufferよりも高速なStringBuilderが存在する1.5以上の環境では、むしろ駄目なコードであるとさえいえます。そのことを、コードレビューの際に控えめに指摘してみたところ、eclipseのデバッガではステップ実行できることから、そのような変換はなされてないのではないかと疑問を呈されてしまいました。
確かに私も話を聞いたことがあるだけの伝聞知識であるにすぎず、実際にどうなるのかをこの目で確認したことがあるわけではありません。これは事実を確認してみる必要がありますね。ということで早速実験してみました。いろんな版数のjavacでコンパイルしてみて、できたclassファイルをjad *2 で逆コンパイルしてみるというものです。なお、いずれの版数のjavacもSunのものを使用しました。
javaコード
import java.util.*; public class test { public static void main(String[] args) { Map m = new HashMap(); m.put("a","b"); int a1 = 789; String a = "aaa" + a1 + "aaaa" + "cccc" + m.get("aa") + "dddd"; System.out.println( a ); } }
逆コンパイル結果(1.3)
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: annotate fullnames lnc // Source File Name: test.java import java.io.PrintStream; import java.util.HashMap; import java.util.Map; public class test { public test() { /* 3*/ // 0 0:aload_0 /* 3*/ // 1 1:invokespecial #1 <Method void Object()> /* 3*/ // 2 4:return } public static void main(java.lang.String args[]) { /* 7*/ java.util.HashMap hashmap = new HashMap(); /* 7*/ // 0 0:new #2 <Class java.util.HashMap> /* 7*/ // 1 3:dup /* 7*/ // 2 4:invokespecial #3 <Method void HashMap()> /* 7*/ // 3 7:astore_1 /* 8*/ hashmap.put("a", "b"); /* 8*/ // 4 8:aload_1 /* 8*/ // 5 9:ldc1 #4 <String "a"> /* 8*/ // 6 11:ldc1 #5 <String "b"> /* 8*/ // 7 13:invokeinterface #6 <Method java.lang.Object java.util.Map.put(java.lang.Object, java.lang.Object)> /* 8*/ // 8 18:pop /* 10*/ char c = '\u0315'; /* 10*/ // 9 19:sipush 789 /* 10*/ // 10 22:istore_2 /* 12*/ java.lang.String s = "aaa" + (int)c + "aaaa" + "cccc" + hashmap.get("aa") + "dddd"; /* 12*/ // 11 23:new #7 <Class java.lang.StringBuffer> /* 12*/ // 12 26:dup /* 12*/ // 13 27:invokespecial #8 <Method void StringBuffer()> /* 12*/ // 14 30:ldc1 #9 <String "aaa"> /* 12*/ // 15 32:invokevirtual #10 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String)> /* 12*/ // 16 35:iload_2 /* 12*/ // 17 36:invokevirtual #11 <Method java.lang.StringBuffer java.lang.StringBuffer.append(int)> /* 12*/ // 18 39:ldc1 #12 <String "aaaa"> /* 12*/ // 19 41:invokevirtual #10 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String)> /* 12*/ // 20 44:ldc1 #13 <String "cccc"> /* 12*/ // 21 46:invokevirtual #10 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String)> /* 12*/ // 22 49:aload_1 /* 12*/ // 23 50:ldc1 #14 <String "aa"> /* 12*/ // 24 52:invokeinterface #15 <Method java.lang.Object java.util.Map.get(java.lang.Object)> /* 12*/ // 25 57:invokevirtual #16 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.Object)> /* 12*/ // 26 60:ldc1 #17 <String "dddd"> /* 12*/ // 27 62:invokevirtual #10 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String)> /* 12*/ // 28 65:invokevirtual #18 <Method java.lang.String java.lang.StringBuffer.toString()> /* 12*/ // 29 68:astore_3 /* 19*/ java.lang.System.out.println(s); /* 19*/ // 30 69:getstatic #19 <Field java.io.PrintStream java.lang.System.out> /* 19*/ // 31 72:aload_3 /* 19*/ // 32 73:invokevirtual #20 <Method void java.io.PrintStream.println(java.lang.String)> /* 20*/ // 33 76:return } }
逆コンパイル結果(1.4)
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate_fullnames lnc // Source File Name: test.java import java.io.PrintStream; import java.util.HashMap; import java.util.Map; public class test { public test() { /* 3*/ // 0 0:aload_0 /* 3*/ // 1 1:invokespecial #1 <Method void Object()> /* 3*/ // 2 4:return } public static void main(String args[]) { /* 7*/ HashMap hashmap = new HashMap(); /* 7*/ // 0 0:new #2 <Class java.util.HashMap> /* 7*/ // 1 3:dup /* 7*/ // 2 4:invokespecial #3 <Method void HashMap()> /* 7*/ // 3 7:astore_1 /* 8*/ hashmap.put("a", "b"); /* 8*/ // 4 8:aload_1 /* 8*/ // 5 9:ldc1 #4 <String "a"> /* 8*/ // 6 11:ldc1 #5 <String "b"> /* 8*/ // 7 13:invokeinterface #6 <Method java.lang.Object java.util.Map.put(java.lang.Object, java.lang.Object)> /* 8*/ // 8 18:pop /* 10*/ char c = '\u0315'; /* 10*/ // 9 19:sipush 789 /* 10*/ // 10 22:istore_2 /* 12*/ String s = "aaa" + (int)c + "aaaa" + "cccc" + hashmap.get("aa") + "dddd"; /* 12*/ // 11 23:new #7 <Class java.lang.StringBuffer> /* 12*/ // 12 26:dup /* 12*/ // 13 27:invokespecial #8 <Method void StringBuffer()> /* 12*/ // 14 30:ldc1 #9 <String "aaa"> /* 12*/ // 15 32:invokevirtual #10 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String)> /* 12*/ // 16 35:iload_2 /* 12*/ // 17 36:invokevirtual #11 <Method java.lang.StringBuffer java.lang.StringBuffer.append(int)> /* 12*/ // 18 39:ldc1 #12 <String "aaaa"> /* 12*/ // 19 41:invokevirtual #10 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String)> /* 12*/ // 20 44:ldc1 #13 <String "cccc"> /* 12*/ // 21 46:invokevirtual #10 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String)> /* 12*/ // 22 49:aload_1 /* 12*/ // 23 50:ldc1 #14 <String "aa"> /* 12*/ // 24 52:invokeinterface #15 <Method java.lang.Object java.util.Map.get(java.lang.Object)> /* 12*/ // 25 57:invokevirtual #16 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.Object)> /* 12*/ // 26 60:ldc1 #17 <String "dddd"> /* 12*/ // 27 62:invokevirtual #10 <Method java.lang.StringBuffer java.lang.StringBuffer.append(java.lang.String)> /* 12*/ // 28 65:invokevirtual #18 <Method java.lang.String java.lang.StringBuffer.toString()> /* 12*/ // 29 68:astore_3 /* 19*/ System.out.println(s); /* 19*/ // 30 69:getstatic #19 <Field java.io.PrintStream java.lang.System.out> /* 19*/ // 31 72:aload_3 /* 19*/ // 32 73:invokevirtual #20 <Method void java.io.PrintStream.println(java.lang.String)> /* 20*/ // 33 76:return } }
逆コンパイル結果(1.5)
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate_fullnames lnc // Source File Name: test.java import java.io.PrintStream; import java.util.HashMap; import java.util.Map; public class test { public test() { /* 3*/ // 0 0:aload_0 /* 3*/ // 1 1:invokespecial #1 <Method void Object()> /* 3*/ // 2 4:return } public static void main(String args[]) { /* 7*/ HashMap hashmap = new HashMap(); /* 7*/ // 0 0:new #2 <Class java.util.HashMap> /* 7*/ // 1 3:dup /* 7*/ // 2 4:invokespecial #3 <Method void HashMap()> /* 7*/ // 3 7:astore_1 /* 8*/ hashmap.put("a", "b"); /* 8*/ // 4 8:aload_1 /* 8*/ // 5 9:ldc1 #4 <String "a"> /* 8*/ // 6 11:ldc1 #5 <String "b"> /* 8*/ // 7 13:invokeinterface #6 <Method java.lang.Object java.util.Map.put(java.lang.Object, java.lang.Object)> /* 8*/ // 8 18:pop /* 10*/ char c = '\u0315'; /* 10*/ // 9 19:sipush 789 /* 10*/ // 10 22:istore_2 /* 12*/ String s = (new StringBuilder()).append("aaa").append(c).append("aaaa").append("cccc").append(hashmap.get("aa")).append("dddd").toString(); /* 12*/ // 11 23:new #7 <Class java.lang.StringBuilder> /* 12*/ // 12 26:dup /* 12*/ // 13 27:invokespecial #8 <Method void StringBuilder()> /* 12*/ // 14 30:ldc1 #9 <String "aaa"> /* 12*/ // 15 32:invokevirtual #10 <Method java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)> /* 12*/ // 16 35:iload_2 /* 12*/ // 17 36:invokevirtual #11 <Method java.lang.StringBuilder java.lang.StringBuilder.append(int)> /* 12*/ // 18 39:ldc1 #12 <String "aaaa"> /* 12*/ // 19 41:invokevirtual #10 <Method java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)> /* 12*/ // 20 44:ldc1 #13 <String "cccc"> /* 12*/ // 21 46:invokevirtual #10 <Method java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)> /* 12*/ // 22 49:aload_1 /* 12*/ // 23 50:ldc1 #14 <String "aa"> /* 12*/ // 24 52:invokeinterface #15 <Method java.lang.Object java.util.Map.get(java.lang.Object)> /* 12*/ // 25 57:invokevirtual #16 <Method java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.Object)> /* 12*/ // 26 60:ldc1 #17 <String "dddd"> /* 12*/ // 27 62:invokevirtual #10 <Method java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)> /* 12*/ // 28 65:invokevirtual #18 <Method java.lang.String java.lang.StringBuilder.toString()> /* 12*/ // 29 68:astore_3 /* 19*/ System.out.println(s); /* 19*/ // 30 69:getstatic #19 <Field java.io.PrintStream java.lang.System.out> /* 19*/ // 31 72:aload_3 /* 19*/ // 32 73:invokevirtual #20 <Method void java.io.PrintStream.println(java.lang.String)> /* 20*/ // 33 76:return } }
結果
バイトコードレベルでは1.5ならStringBuilder、1.3や1.4ならStringBufferを用いるコードに変換されていました。都市伝説ではなかったようです。古臭い時代遅れの1.3でもきちんと変換していたのが、意外といえば意外でした。争点となったEclipseのデバッガに関しては、埋め込まれたデバッグ情報からなんとかうまくやっているのでしょう。そもそも最近のEclipse JDTコンパイラはSunのものでもないですし……。
*1:そもそもファイルに外だししておくべきなんじゃないの?とも思わなくもありませんでしたが、まあ……