What is the best (idiomatic) way to concatenate Strings in Groovy?
Option 1:
calculateAccountNumber(bank, branch, checkDigit, account) { bank + branch + checkDigit + account }
Option 2:
calculateAccountNumber(bank, branch, checkDigit, account) { "$bank$branch$checkDigit$account" }
I've founded an interesting point about this topic in the old Groovy website: Things you can do but better leave undone.
As in Java, you can concatenate Strings with the "+" symbol. But Java only needs that one of the two items of a "+" expression to be a String, no matter if it's in the first place or in the last one. Java will use the toString() method in the non-String object of your "+" expression. But in Groovy, you just should be safe the first item of your "+" expression implements the plus() method in the right way, because Groovy will search and use it. In Groovy GDK, only the Number and String/StringBuffer/Character classes have the plus() method implemented to concatenate strings. To avoid surprises, always use GStrings.
I always go for the second method (using the GString template), though when there are more than a couple of parameters like you have, I tend to wrap them in ${X}
as I find it makes it more readable.
Running some benchmarks (using Nagai Masato's excellent GBench module) on these methods also shows templating is faster than the other methods:
@Grab( 'com.googlecode.gbench:gbench:0.3.0-groovy-2.0' ) import gbench.* def (foo,bar,baz) = [ 'foo', 'bar', 'baz' ] new BenchmarkBuilder().run( measureCpuTime:false ) { // Just add the strings 'String adder' { foo + bar + baz } // Templating 'GString template' { "$foo$bar$baz" } // I find this more readable 'Readable GString template' { "${foo}${bar}${baz}" } // StringBuilder 'StringBuilder' { new StringBuilder().append( foo ) .append( bar ) .append( baz ) .toString() } 'StringBuffer' { new StringBuffer().append( foo ) .append( bar ) .append( baz ) .toString() } }.prettyPrint()
That gives me the following output on my machine:
Environment =========== * Groovy: 2.0.0 * JVM: Java HotSpot(TM) 64-Bit Server VM (20.6-b01-415, Apple Inc.) * JRE: 1.6.0_31 * Total Memory: 81.0625 MB * Maximum Memory: 123.9375 MB * OS: Mac OS X (10.6.8, x86_64) Options ======= * Warm Up: Auto * CPU Time Measurement: Off String adder 539 GString template 245 Readable GString template 244 StringBuilder 318 StringBuffer 370
So with readability and speed in it's favour, I'd recommend templating ;-)
NB: If you add toString()
to the end of the GString methods to make the output type the same as the other metrics, and make it a fairer test, StringBuilder
and StringBuffer
beat the GString methods for speed. However as GString can be used in place of String for most things (you just need to exercise caution with Map keys and SQL statements), it can mostly be left without this final conversion
Adding these tests (as it has been asked in the comments)
'GString template toString' { "$foo$bar$baz".toString() } 'Readable GString template toString' { "${foo}${bar}${baz}".toString() }
Now we get the results:
String adder 514 GString template 267 Readable GString template 269 GString template toString 478 Readable GString template toString 480 StringBuilder 321 StringBuffer 369
So as you can see (as I said), it is slower than StringBuilder or StringBuffer, but still a bit faster than adding Strings...
But still lots more readable.
Edit after comment by ruralcoder below
Updated to latest gbench, larger strings for concatenation and a test with a StringBuilder initialised to a good size:
@Grab( 'org.gperfutils:gbench:0.4.2-groovy-2.1' ) def (foo,bar,baz) = [ 'foo' * 50, 'bar' * 50, 'baz' * 50 ] benchmark { // Just add the strings 'String adder' { foo + bar + baz } // Templating 'GString template' { "$foo$bar$baz" } // I find this more readable 'Readable GString template' { "${foo}${bar}${baz}" } 'GString template toString' { "$foo$bar$baz".toString() } 'Readable GString template toString' { "${foo}${bar}${baz}".toString() } // StringBuilder 'StringBuilder' { new StringBuilder().append( foo ) .append( bar ) .append( baz ) .toString() } 'StringBuffer' { new StringBuffer().append( foo ) .append( bar ) .append( baz ) .toString() } 'StringBuffer with Allocation' { new StringBuffer( 512 ).append( foo ) .append( bar ) .append( baz ) .toString() } }.prettyPrint()
gives
Environment =========== * Groovy: 2.1.6 * JVM: Java HotSpot(TM) 64-Bit Server VM (23.21-b01, Oracle Corporation) * JRE: 1.7.0_21 * Total Memory: 467.375 MB * Maximum Memory: 1077.375 MB * OS: Mac OS X (10.8.4, x86_64) Options ======= * Warm Up: Auto (- 60 sec) * CPU Time Measurement: On user system cpu real String adder 630 0 630 647 GString template 29 0 29 31 Readable GString template 32 0 32 33 GString template toString 429 0 429 443 Readable GString template toString 428 1 429 441 StringBuilder 383 1 384 396 StringBuffer 395 1 396 409 StringBuffer with Allocation 277 0 277 286