One of my pet Java peeves is that some people religiously avoid the String concatenation operators, +
and +=
, because they are less efficient than the alternatives. It's an urban legend with a very tenuous basis in fact. Yes, there are some situations that requires StringBuffer instead of String. No, that doesn't mean that String concatenation is bad in all cases.
The theory goes like this. Strings are immutable. Thus, when you are concatenating "n" strings together, there must be "n - 1" intermediate String objects created in the process (including the final, complete String). Thus, to avoid dumping a bunch of unwanted String objects onto the garbage-collector, you should use the StringBuffer object instead.
So, by this theory, String a = b + c + d;
is bad code, while String a = new StringBuffer(b).append(c).append(d).toString()
is good code, despite the fact that the former is about a thousand times more readable than the latter.
For as long as I have been using Java, this has not been true. If you look at StringBuffer handling, you'll see the bytecodes that a Java compiler actually produces in those two cases. In most simple string-concatenation cases, the compiler will automatically convert a series of operations on Strings into a series of StringBuffer operations, and then pop the result back into a String.
The only time you need to switch to an explicit StringBuffer is in more complex cases, for example if the concatenation is occurring within a loop (see StringBuffer handling in loops)
Regular String Handling
Tests performed on an OS X 10.2 system running the supplied 1.3.1 compiler, which is basically Sun's compiler. The compiled bytecodes were identical on my Linux box running 1.4.0
The Class
public class Test { private String a = "alpha"; private String b = Beta; private String c = "gamma"; public String test1() { return a + b + c; } public String test2() { StringBuffer s = new StringBuffer(a); s.append(b); s.append(c); return s.toString(); } }
Test1 decompiled:
Method java.lang.String >test1() 0 new #8 <Class java.lang.StringBuffer> 3 dup 4 invokespecial #9 <Method java.lang.StringBuffer()> 7 aload_0 8 getfield #3 <Field java.lang.String a> 11 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 14 aload_0 15 getfield #5 <Field java.lang.String b> 18 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 21 aload_0 22 getfield #7 <Field java.lang.String c> 25 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 28 invokevirtual #11 <Method java.lang.String toString()> 31 areturn
Test2 decompiled
Method java.lang.String test2() 0 new #8 <Class java.lang.StringBuffer> 3 dup 4 aload_0 5 getfield #3 <Field java.lang.String a> 8 invokespecial #12 <Method java.lang.StringBuffer(java.lang.String)> 11 astore_1 12 aload_1 13 aload_0 14 getfield #5 <Field java.lang.String b> 17 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 20 pop 21 aload_1 22 aload_0 23 getfield #7 <Field java.lang.String c> 26 invokevirtual #10 <Method java.lang.StringBuffer append(java.lang.String)> 29 pop 30 aload_1 31 invokevirtual #11 <Method java.lang.String toString()> 34 areturn
String Handling in a Loop
The Source
public class Test2 { private String a = "alpha"; private String b = "beta"; private String c = "gamma"; public String test1() { String r = ""; for (int i = 0; i < 10; i++) { r += "foo"; } return r; } public String test2() { StringBuffer r = new StringBuffer(); for (int i = 0; i < 10; i++) { r.append("foo"); } return r.toString(); } }
The Bytecodes
Method java.lang.String test1() 0 ldc #8 <String ""> 2 astore_1 3 iconst_0 4 istore_2 5 goto 31 8 new #9 <Class java.lang.StringBuffer> 11 dup 12 invokespecial #10 <Method java.lang.StringBuffer()> 15 aload_1 16 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)> 19 ldc #12 <String "foo"> 21 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)> 24 invokevirtual #13 <Method java.lang.String toString()> 27 astore_1 28 iinc 2 1 31 iload_2 32 bipush 10 34 if_icmplt 8 37 aload_1 38 areturn Method java.lang.String test2() 0 new #9 <Class java.lang.StringBuffer> 3 dup 4 invokespecial #10 <Method java.lang.StringBuffer()> 7 astore_1 8 iconst_0 9 istore_2 10 goto 23 13 aload_1 14 ldc #12 <String "foo"> 16 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)> 19 pop 20 iinc 2 1 23 iload_2 24 bipush 10 26 if_icmplt 13 29 aload_1 30 invokevirtual #13 <Method java.lang.String toString()> 33 areturn