String concatenation with Groovy
Asked Answered
R

3

107

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.

Remediosremedy answered 6/7, 2012 at 9:18 Comment(0)
C
131

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
Cornelison answered 6/7, 2012 at 9:33 Comment(11)
I don't disagree with using GString templates for legibility, but you should re-run the tests with .toString() appended to the two GString tests. My run shows that they then perform almost the same as String adder. My guess is that the test you've run doesn't actually handle the concatenation, so it's just creating a GString object and storing the references. StringBuilder is still the fastest, hands down, if you need a String at some point.Delirious
@Delirious did you read the NB bit at the bottom of the answer? (added the tests and the output now...)Cornelison
I missed the second half of that somehow! Of course, even if you leave the GString "as-is", at some point it has to be converted into a true String, (even just to print it out), so the true timing is the last set. In the end the legibility of GString templates beats StringBuilder when the timing is this close, so it's moot. :-)Delirious
@Delirious Ahhh yes, as always, there are lies, damned lies and benchmarks ;-) The readability is key here I feel and as we're already using Groovy, we've stated that bare-metal performance is not our major consideration ;-)Cornelison
Yeah, one of the great advantages of GStrings is that they're not converted to strings until the last moment. Which means, for example, if you log a GString with a logger like log4j below the logging threshold, the GString never gets converted at all.Typhoon
What is missing from the test is StringBuilder with calculated capacity. The reason is that the foo+bar+baz will cause one or two buffer expansions which adds to the time.Partner
@Partner this may be true in general, but StringBuilder (in Java 7 at least) is constructed with a starting capacity of 16, and we're only adding 9 chars so in this case, it's slower to set a larger capacity (or calculate an accurate capacity) than to leave it with the default.Cornelison
I ran my own benchmarks using a real problem where the final string was about 256 chars long, but variable length of course. StringBuilder with capacity calculation was 4x faster than GString. Using 1 mil iterations. Bench was in Groovy using delta of currentTimeMillis. I am gonna also try gbench, never used it before.Partner
@Partner yeah the component string size was misleading... updated answerCornelison
Interesting. I like the bench change. I am not cleat about the GString VS GString with toString. Is using toString avoidable. If thats the case GString template alone is faster by far. Am I missing something? Also what about JIT optimizations when it comes to this issue?Partner
.concat is the fastest method in all cases. If you’d like a more groovy version, a << b is faster than a + b.groovy-programming.com/post/5732276432Lanai
B
48
def my_string = "some string"
println "here: " + my_string 

Not quite sure why the answer above needs to go into benchmarks, string buffers, tests, etc.

Ballou answered 12/2, 2019 at 16:1 Comment(1)
Upvote for simplicity. I just need to concatenate two strings. lolLeslileslie
T
2

Reproducing tim_yates answer on current hardware and adding leftShift() and concat() method to check the finding:

  'String leftShift' {
    foo << bar << baz
  }
  'String concat' {
    foo.concat(bar)
       .concat(baz)
       .toString()
  }

The outcome shows concat() to be the faster solution for a pure String, but if you can handle GString somewhere else, GString template is still ahead, while honorable mention should go to leftShift() (bitwise operator) and StringBuffer() with initial allocation:

Environment
===========
* Groovy: 2.4.8
* JVM: OpenJDK 64-Bit Server VM (25.191-b12, Oracle Corporation)
    * JRE: 1.8.0_191
    * Total Memory: 238 MB
    * Maximum Memory: 3504 MB
* OS: Linux (4.19.13-300.fc29.x86_64, amd64)

Options
=======
* Warm Up: Auto (- 60 sec)
* CPU Time Measurement: On

                                    user  system  cpu  real

String adder                         453       7  460   469
String leftShift                     287       2  289   295
String concat                        169       1  170   173
GString template                      24       0   24    24
Readable GString template             32       0   32    32
GString template toString            400       0  400   406
Readable GString template toString   412       0  412   419
StringBuilder                        325       3  328   334
StringBuffer                         390       1  391   398
StringBuffer with Allocation         259       1  260   265
Taxdeductible answered 11/1, 2019 at 12:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.