[string] String concatenation with Groovy

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.

This question is related to string groovy idioms string-concatenation gstring

The answer is


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

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

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.


Examples related to string

How to split a string in two and store it in a field String method cannot be found in a main class method Kotlin - How to correctly concatenate a String Replacing a character from a certain index Remove quotes from String in Python Detect whether a Python string is a number or a letter How does String substring work in Swift How does String.Index work in Swift swift 3.0 Data to String? How to parse JSON string in Typescript

Examples related to groovy

Jenkins pipeline how to change to another folder groovy.lang.MissingPropertyException: No such property: jenkins for class: groovy.lang.Binding Run bash command on jenkins pipeline Try-catch block in Jenkins pipeline script How to print a Groovy variable in Jenkins? Jenkins pipeline if else not working How to set and reference a variable in a Jenkinsfile Jenkins: Can comments be added to a Jenkinsfile? How to define and use function inside Jenkins Pipeline config? Jenkins: Cannot define variable in pipeline stage

Examples related to idioms

String concatenation with Groovy Check whether a variable is a string in Ruby How to implement the factory method pattern in C++ correctly How can I loop through a C++ map of maps? Get the key corresponding to the minimum value within a dictionary How do I reverse an int array in Java? When to use std::size_t? Are one-line 'if'/'for'-statements good Python style? What is the pythonic way to detect the last element in a 'for' loop? Python: most idiomatic way to convert None to empty string?

Examples related to string-concatenation

How do I concatenate strings? How do I concatenate strings in Swift? How to set the id attribute of a HTML element dynamically with angularjs (1.x)? C++ String Concatenation operator<< how to use concatenate a fixed string and a variable in Python How do I concatenate strings and variables in PowerShell? Concatenating variables in Bash SQL NVARCHAR and VARCHAR Limits Concatenating string and integer in python PHP string concatenation

Examples related to gstring

String concatenation with Groovy