Concatenação de string com Groovy

91

Qual é a melhor maneira (idiomática) de concatenar Strings no Groovy?

Opção 1:

calculateAccountNumber(bank, branch, checkDigit, account) {
    bank + branch + checkDigit + account
}

Opção 2:

calculateAccountNumber(bank, branch, checkDigit, account) {
    "$bank$branch$checkDigit$account"
}

Eu descobri um ponto interessante sobre esse tópico no antigo site do Groovy: Coisas que você pode fazer, mas é melhor deixar por fazer.

Como em Java, você pode concatenar Strings com o símbolo "+". Mas Java só precisa que um dos dois itens de uma expressão "+" seja uma String, não importa se está no primeiro lugar ou no último. Java usará o método toString () no objeto não String de sua expressão "+". Mas no Groovy, você deve estar seguro de que o primeiro item de sua expressão "+" implementa o método plus () da maneira correta, porque o Groovy irá pesquisá-lo e usá-lo. No Groovy GDK, apenas as classes Number e String / StringBuffer / Character têm o método plus () implementado para concatenar strings. Para evitar surpresas, sempre use GStrings.

Arturo Herrero
fonte

Respostas:

122

Eu sempre escolho o segundo método (usando o modelo GString), embora quando há mais do que alguns parâmetros como você, eu tendo a envolvê-los, ${X}pois acho que os torna mais legíveis.

Executar alguns benchmarks (usando o excelente módulo GBench de Nagai Masato ) nesses métodos também mostra que a modelagem é mais rápida do que os outros métodos:

@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()

Isso me dá a seguinte saída em minha máquina:

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

Portanto, com legibilidade e velocidade a seu favor, eu recomendo criar modelos ;-)

NB: Se você adicionar toString()ao final dos métodos GString para tornar o tipo de saída o mesmo que as outras métricas, torná-lo um teste mais justo StringBuildere StringBuffervencer os métodos GString para velocidade. No entanto, como GString pode ser usado no lugar de String para a maioria das coisas (você só precisa ter cuidado com as chaves de mapa e instruções SQL), ele pode ser deixado na maioria das vezes sem esta conversão final

Adicionando esses testes (como foi perguntado nos comentários)

  'GString template toString' {
    "$foo$bar$baz".toString()
  }
  'Readable GString template toString' {
    "${foo}${bar}${baz}".toString()
  }

Agora temos os resultados:

String adder                        514
GString template                    267
Readable GString template           269
GString template toString           478
Readable GString template toString  480
StringBuilder                       321
StringBuffer                        369

Como você pode ver (como eu disse), é mais lento que StringBuilder ou StringBuffer, mas ainda um pouco mais rápido do que adicionar Strings ...

Mas ainda muito mais legível.

Editar após o comentário de ruralcoder abaixo

Atualizado para o gbench mais recente, strings maiores para concatenação e um teste com um StringBuilder inicializado com um bom tamanho:

@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()

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
tim_yates
fonte
3
Não discordo do uso de modelos GString para legibilidade, mas você deve executar novamente os testes com o .toString()anexo aos dois testes GString. Minha corrida mostra que eles têm quase o mesmo desempenho String adder. Meu palpite é que o teste que você executou não lida com a concatenação, então ele está apenas criando um objeto GString e armazenando as referências. StringBuilderainda é o mais rápido, sem dúvida, se você precisar de um Stringem algum momento.
OverZealous
1
Eu perdi a segunda metade disso de alguma forma! Claro, mesmo se você deixar o GString"como está", em algum ponto ele terá que ser convertido em verdadeiro String(mesmo que apenas para imprimi-lo), então o tempo verdadeiro é o último conjunto. No final, a legibilidade dos GStringmodelos bate StringBuilderquando o tempo está próximo, então é discutível. :-)
Excesso de zelo
2
@OverZealous Ahhh sim, como sempre, existem mentiras, mentiras malditas e benchmarks ;-) A legibilidade é a chave aqui, eu sinto e como já estamos usando Groovy, afirmamos que o desempenho bare-metal não é nossa principal consideração; -)
tim_yates
1
Sim, uma das grandes vantagens dos GStrings é que eles não são convertidos em strings até o último momento. O que significa, por exemplo, se você registrar um GString com um logger como o log4j abaixo do limite de registro, o GString nunca será convertido.
ataylor
1
O que está faltando no teste é StringBuilder com capacidade calculada. A razão é que foo + bar + baz causará uma ou duas expansões de buffer que aumentam o tempo.
ruralcoder
19
def my_string = "some string"
println "here: " + my_string 

Não tenho certeza de por que a resposta acima precisa ir para benchmarks, buffers de string, testes, etc.

Snowcrash
fonte
1
Vote positivamente para simplicidade. Eu só preciso concatenar duas strings. lol
Harperville
1

Reproduzindo a resposta tim_yates no hardware atual e adicionando os métodos leftShift () e concat () para verificar a descoberta:

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

O resultado mostra que concat () é a solução mais rápida para uma String pura, mas se você pode lidar com GString em outro lugar, o modelo GString ainda está à frente, enquanto a menção honrosa deve ir para leftShift () (operador bit a bit) e StringBuffer () com inicial alocação:

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
toroc
fonte