stringgroovyidiomsstring-concatenationgstring

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.


Solution

  • 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