The StringBuffer Myth

September 4, 2002 5:05 PM

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

Previously: Wed, 04 Sep 2002 03:20:01 GMT

Next: Wed, 04 Sep 2002 10:40:48 GMT