Most Java programmers know to use a StringBuffer (or a JDK 1.5 StringBuilder) when concatenating Strings in Java,
but it occurred to me that the compiler might be able to optimize some of these calls.
For instance, look at the following code:
StringBuffer buf = new StringBuffer();
buf.append("one" + "two");
There is an obvious optimization there: combine “one” and “two” into a single String before appending them to the
StringBuffer.
Sure enough:
L0 (0)
NEW StringBuffer
DUP
INVOKESPECIAL StringBuffer.() : void
ASTORE 0: buf
L1 (5)
ALOAD 0: buf
LDC "onetwo"
INVOKEVIRTUAL StringBuffer.append(String) : StringBuffer
POP
Note the LDC "onetwo" line – the compiler has combined the two strings in the bytecode itself.
However, the following code:
String one = "str1";
StringBuffer buf = new StringBuffer();
buf.append(one + "two");
gives:
L0 (0)
LDC "str1"
ASTORE 0: one
L1 (3)
NEW StringBuffer
DUP
INVOKESPECIAL StringBuffer.() : void
ASTORE 1: buf
L2 (8)
ALOAD 1: buf
NEW StringBuffer
DUP
ALOAD 0: one
INVOKESTATIC String.valueOf(Object) : String
INVOKESPECIAL StringBuffer.(String) : void
LDC "two"
INVOKEVIRTUAL StringBuffer.append(String) : StringBuffer
INVOKEVIRTUAL StringBuffer.toString() : String
INVOKEVIRTUAL StringBuffer.append(String) : StringBuffer
POP
Hmm – that isn't good at all. The code one + "two" causes the compiler to create a new StringBuffer, concatenate
the two Strings, call toString on the temporary StringBuffer and append that to the original StringBuffer. Looks like that
should be written:
String one = "str1";
StringBuffer buf = new StringBuffer();
buf.append(one);
buf.append("two");
which gives:
L0 (0)
LDC "str1"
ASTORE 0: one
L1 (3)
NEW StringBuffer
DUP
INVOKESPECIAL StringBuffer.() : void
ASTORE 1: buf
L2 (8)
ALOAD 1: buf
ALOAD 0: one
INVOKEVIRTUAL StringBuffer.append(String) : StringBuffer
POP
L3 (13)
ALOAD 1: buf
LDC "two"
INVOKEVIRTUAL StringBuffer.append(String) : StringBuffer
POP
Much better!
There is one final (pun intended) change that I found interesting:
final String one = "str1";
StringBuffer buf = new StringBuffer();
buf.append(one + "two");
gives:
L0 (0)
LDC "str1"
ASTORE 0: one
L1 (3)
NEW StringBuffer
DUP
INVOKESPECIAL StringBuffer.() : void
ASTORE 1: buf
L2 (8)
ALOAD 1: buf
LDC "str1two"
INVOKEVIRTUAL StringBuffer.append(String) : StringBuffer
POP
Nice!
Obviously most of this is pretty logical and well known by most Java programmers. I found it interesting to see exactly what
optimizations the complier (as opposed to the JVM) is doing though. (Note that these experiments were done using the
Eclipse 3.0 JDK 1.4 complier. Other compiler optimizations may vary)