Allan Caplan writes another fine PMD rule; this one, ConsecutiveLiteralAppends, catches code like this:
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append("world");
Those two StringBuffer.append invocations can be combined into one statement. Doing so knocks 17 bytes off the classfile size and eliminates a few instructions, including an invokevirtual. And of course it's a bit faster, too!
There's a threshold at which combining append calls makes the code less readable, so, as always, use your own judgement when applying this rule. But maybe this will turn up a few places where you can consolidate things - especially inside loops.
Thanks again to Allan for the nice rule! And props to him for writing a slew of unit tests for it; that should really help to reduce false positives.
Support PMD, get the book!
Are you saying its better to do String blah = "hello" + "world" ? Or do append("hello" + "world")?
Also, wouldn't thid depend heavily upon the JVM used?
Posted by: Rob | January 09, 2006 at 05:18 PM
he is saying that use sb.append("hello").append("world")
Posted by: nec | January 09, 2006 at 05:26 PM
This code is actually in violation:
sb.append("hello").append("world")
Since you already know what can be done, you're better writing
sb.append("helloworld")
This code:
String blah = "hello" + "world"?
append("hello" + "world")?
Will actually be optimized by the compiler, where the first example won't.
Posted by: Allan Caplan | January 09, 2006 at 05:31 PM
Rob - Yup, the idea is to consolidate those two append calls into sb.append("hello world"). That'll eliminate the extra method call...
Posted by: tomcopeland | January 09, 2006 at 05:32 PM
nec hit it on the head. He is saying:
sb.append("hello").append("world")
The example he gives isn't the best since he's using two literals. If they were variables (like in real world development) the statement would be:
sb.append(var1).append(var2);
instead of
sb.append(var1 + var2);
or
sb.append(var1);
sb.append(var2);
The append method returns the object "sb" just for this purpose.
Posted by: Jack | January 09, 2006 at 11:07 PM
Hi Jack - This rule won't flag consecutive appends that involve variables; it only triggers on literal appends. So the method chaining idiom should be OK... and I agree, it's quite handy.
Posted by: tomcopeland | January 10, 2006 at 09:24 AM
What about a rule that covers an empty StringBuffer constructor followed by a literal append. Wouldn't
StringBuffer sb = new StringBuffer("helloworld")
save a few more cycles?
Posted by: David Hall | January 10, 2006 at 09:26 AM
David - Yup, that's true, that would be another good rule; can you put in an RFE for it here?
https://sourceforge.net/tracker/?group_id=56262&atid=479924
Posted by: tomcopeland | January 10, 2006 at 09:33 AM
> Are you saying its better to do String > blah = "hello" + "world" ?
> Or do append("hello" + "world")?
It's better to use '+' sign outside a loop:
String greeting = "Hello world" + " also known as " + theThirdPlanet;
Not only it's faster, but it's also easier to read. Only JDK 1.0 doesn't optimize that into StringBuffer with append.
More about StringBuffer Myth:
http://fishbowl.pastiche.org/2002/09/04/the_stringbuffer_myth
Posted by: Artti | January 25, 2006 at 01:13 PM
After I use jdk1.5, I leave this code;
String a = "Hello "+var1;
Hope jdk1.5 can optimized to use StringBuilder instead of StringBuffer.
It will better if StringBuilder does extends from StringBuffer, because some method that pass StringBuffer as argument will get benefits of StringBuilder incase of called in local thread.
Posted by: eswat | January 04, 2007 at 04:53 AM
From what I understand, under a modern JVM (Java 5+). StringBuilder is not actually any faster than StringBuffer in most cases. Although StringBuilder provides extra synchronisation, all that synchronisation is removed because the JVM knows when it is pointless. If synchronisation is needed (for the less than 1% of cases that you might use it in this way?) then you should be using StringBuffer instead of StringBuilder anyway (unless synchronising on another object perhaps)! :)
Posted by: Kieron Wilkinson | January 10, 2007 at 06:19 AM
Kieron - Could be. Any idea if anyone's done some benchmarks along these lines? I've been looking at adding StringBuilder usage to JavaCC if the JDK_VERSION flag is set to "1.5", and no sense doing that if it really won't help....
Posted by: tomcopeland | January 10, 2007 at 06:40 AM
Hi Tom,
Okay, I wrote this program to try to figure it out. Generally I hate micro-benchmarks, but I think I have made the appropriate allowances to prevent the JVM from optimising out my code.
public class Test {
public static void main(String[] args) {
try { Thread.sleep(5000); } catch (InterruptedException e) { }
testBuilder();
testBuffer();
testBuilder();
testBuffer();
testBuilder();
testBuffer();
testBuilder();
testBuffer();
testBuilder();
testBuffer();
}
private static void testBuilder() {
final long start = System.nanoTime();
final StringBuilder sb = new StringBuilder();
for (int i=0; i<5000000; i++) {
sb.append("T ");
}
final long end = System.nanoTime();
System.out.println("Builder: " + (end-start)/1000000);
System.out.println(sb.toString().length());
}
private static void testBuffer() {
final long start = System.nanoTime();
final StringBuffer sb = new StringBuffer();
for (int i=0; i<5000000; i++) {
sb.append("T ");
}
final long end = System.nanoTime();
System.out.println("Buffer: " + (end-start)/1000000);
System.out.println(sb.toString().length());
}
}
I got some interesting results!
Under Java 6 I got:
Builder: 413
Buffer: 368
Builder: 359
Buffer: 369
Builder: 285
Buffer: 317
Builder: 300
Buffer: 313
Builder: 309
Buffer: 323
And under Java 5 I got:
Builder: 403
Buffer: 685
Builder: 309
Buffer: 669
Builder: 311
Buffer: 669
Builder: 315
Buffer: 672
Builder: 316
Buffer: 667
So, I guess what I heard was correct, but it is a Java 6 specific optimisation. I guess you should ask yourself if it is worth optimising something that is irrelevent for Java 6 and onwards anyway... My thought it is that it probably not, and will just complicate your code.
Posted by: Kieron Wilkinson | January 16, 2007 at 08:10 AM
Kieron - Thanks for the benchmarking! Yup, we should probably disable this rule if PMD is being run on code that's intended to be run on a Java 1.6 VM.
Posted by: tomcopeland | January 16, 2007 at 08:21 AM
Keiron - Oh wait, I see, we were talking about StringBuilder vs StringBuffer, not the PMD rule ConsecutiveLiteralAppends. Although, the performance boost ConsecutiveLiteralAppends gives you may well disappear in Java 1.6, too... haven't tested that.
Posted by: tomcopeland | January 16, 2007 at 08:25 AM
Actually, I had a compiling error when putting all the .append() together. There are 506 .apppend(). The error happened under JDK 1.5.0_14-b03. The problem will not happen if use multiple lines. So use this technique carefully.
The error is: java.lang.StackOverflowErrorat com.sun.tools.javac.comp.Lower.visitApply(Lower.java:2414)at com.sun.tools.javac.tree.Tree$Apply.accept(Tree.java:813)at com.sun.tools.javac.comp.Lower.translate(Lower.java:1881)at com.sun.tools.javac.comp.Lower.visitSelect(Lower.java:3013)at com.sun.tools.javac.tree.Tree$Select.accept(Tree.java:987)
Posted by: Paul | May 02, 2008 at 02:54 PM