GStrings

Strings that are declared inside double-quotes (i.e. either single double-quotes or triple double-quotes for multi-line strings) can contain arbitrary expressions inside them as shown above using the ${expression} syntax in a similar way to JSP EL, Velocity and Jexl. Any valid Groovy expression can be enclosed in the ${...} including method calls etc. GStrings are defined the same way as normal Strings would be created in Java. Here is a simple example involving a simple variable and an expression:
foxtype = 'quick'
foxcolor = ['b', 'r', 'o', 'w', 'n']
println "The $foxtype ${foxcolor.join()} fox"
// => The quick brown fox
What actually happens is whenever a string expression contains a ${...} expression then rather than a normal java.lang.String instance, a GString object is created which contains the text and values used inside the String.

En una GString las interpolaciones se hacen mediante una estructura de template en la que en los puntos de interpolación se introducen las referencias al manejador de la sustitución.

generaciondecodigos@nereida:~/src/groovy/strings$ cat -n Gstring.groovy
     1  #!/usr/bin/env groovy
     2  what = new StringBuffer('fence')
     3  text = "The cow jumped over the $what"
     4  println text
     5
     6  what.replace(0,5, "moon")
     7  println text
generaciondecodigos@nereida:~/src/groovy/strings$ ./Gstring.groovy
The cow jumped over the fence
The cow jumped over the moon
Este ejemplo funciona porque no hemos cambiado la referencia sino el referente (lo referenciado por $what).

Hemos usado el método replace de la clase StringBuffer:

replace

public StringBuffer replace(int start,
                            int end,
                            String str)

Replaces the characters in a substring of this StringBuffer with
characters in the specified String. The substring begins at the
specified start and extends to the character at index end - 1 or
to the end of the StringBuffer if no such character exists. First
the characters in the substring are removed and then the specified
String is inserted at start. (The StringBuffer will be lengthened
to accommodate the specified String if necessary.)

    Parameters:
        start - The beginning index, inclusive.
        end - The ending index, exclusive.
        str - String that will replace previous contents. 
    Returns:
        This string buffer. 
    Throws:
        StringIndexOutOfBoundsException - if start is negative, greater than length(), or greater than end.
    Since:
        1.2

Veamos que ocurre si cambiamos las variables referenciadas en la GString:

generaciondecodigos@nereida:~/src/groovy/strings$ cat -n lazyeval.groovy 
     1  #!/usr/bin/env groovy
     2  
     3  price = 568.23
     4  company = 'Google'
     5  quote = "Today $company stock closed at $price"
     6  println quote
     7  
     8  stocks = [ Apple : 130.01, Microsoft : 35.95 ]
     9  stocks.each { key, val ->
    10    company = key; val = price;
    11    println quote
    12  }
Lo que ocurre es que quote no cambia:
generaciondecodigos@nereida:~/src/groovy/strings$ ./lazyeval.groovy 
Today Google stock closed at 568.23
Today Google stock closed at 568.23
Today Google stock closed at 568.23

La siguiente solución al problema sustituye las referencias a variables por referencias a clausuras que se ejecutan en el momento de la interpolación:

generaciondecodigos@nereida:~/src/groovy/strings$ cat -n lazyeval2.groovy
     1        #!/usr/bin/env groovy
     2  
     3  pricec = { it.write("$price") }
     4  companyc =  { it.write(company) }
     5  
     6  price = 568.23
     7  company = 'Google'
     8  
     9  quote = "Today ${companyc} stock closed at ${pricec}"
    10  println quote
    11  
    12  stocks = [ Apple : 130.01, Microsoft : 35.95 ]
    13  stocks.each { key, value ->
    14    company = key
    15    price = value
    16    println quote
    17  }
Sigue una ejecución:
generaciondecodigos@nereida:~/src/groovy/strings$ ./lazyeval2.groovy 
Today Google stock closed at 568.23
Today Apple stock closed at 130.01
Today Microsoft stock closed at 35.95
La clausura pricec recibe en it un objeto de la clase java.io.StringWriter. La clase java.io.StringWriter provee un flujo de caracteres que recolecta su salida en una cadena. Así las líneas:
     3  pricec = { it.write("$price") }
     4  companyc =  { it.write(company) }
completan la formación de la cadena quote.

El siguiente ejemplo ilustra el uso de la clase java.io.StringWriter:

generaciondecodigos@nereida:~/src/groovy/strings$ cat -n useStringBuffer.groovy 
     1  #!/usr/bin/env groovy
     2  // Create a StringWriter object.
     3  StringWriter writer = new StringWriter();
     4  
     5  // Write some data to the StringWriter object.
     6  // Start with the 7th character and write to the end.
     7  String s = "Hello World";
     8  writer.write("Hello World", 6, s.length() - 6);
     9  
    10  // Print out the contents of the StringWriter buffer.
    11  println "The StringWriter buffer contains: ${writer.toString()}";
generaciondecodigos@nereida:~/src/groovy/strings$ ./useStringBuffer.groovy 
The StringWriter buffer contains: World

Esto es lo que dice el manual de Groovy al respecto

GString can involve lazy evaluation so it's not until the toString() method is invoked that the GString is evaluated. This lazy evaluation is useful for things like logging as it allows the calculation of the string, the calls to toString() on the values, and the concatenation of the different strings to be done lazily if at all. Here is an example that illustrates lazy evaluation:

    println new Date()
    x = "It is currently ${ new Date() }"
    assert x.values[0] instanceof Date
    y = "It is currently ${ writer -> writer << new Date() }"
    assert y.values[0] instanceof Closure
    sleep 5000
    println x
    println y

which outputs the following:

    Thu Apr 17 23:18:17 EST 2008
    It is currently Thu Apr 17 23:18:17 EST 2008
    It is currently Thu Apr 17 23:18:22 EST 2008

To explain this output, we need to understand additional rules about GStrings. Each value in the GString (where $ appears) is the result of an expression. Most often, the expression is evaluated resulting in an Object, as in all the examples above except for the case of the variable y in which the expression is a Closure A Groovy Closure is like a "code block" or a method pointer. It is a piece of code that is defined and then executed at a later point. It has some special properties like implicit variables, support for currying and support for free variables.

Only when the GString is converted into a String (as for println) is the expression's result converted and incorporated into the result String Each of the values to be substituted into the GStringlang/String. Each of the values to be substituted into the GString.html is obtained by applying one of the following methods:

So, in the example above, x contains an instance of Date resulting from new Date() being invoked at the time x was assigned.

When we print x later, that calculated value is placed in the appropriate place in the GString and output giving us the date at GString definition time.

The other GString, y, contains a 1 parameter closure which means that the closure will be stored away in the GString. When we print out y, the closure will be invoked and give the date at printing time.

Véase una ejecución de un programa basado en el manual:

generaciondecodigos@nereida:~/src/groovy/strings$ cat -n lazygstrings.groovy
     1  println "Starting at ${new Date()}"
     2
     3  sleep(3000)
     4  x = "A few seconds later is ${ new Date() }"
     5  assert x.values[0] instanceof Date
     6
     7  y = "A few more seconds is ${ writer -> writer << new Date() }"
     8  assert y.values[0] instanceof Closure
     9
    10  println x
    11  sleep(3000)
    12  println y

generaciondecodigos@nereida:~/src/groovy/strings$ groovy lazygstrings.groovy
Starting at Tue Nov 17 11:03:42 WET 2009
A few seconds later is Tue Nov 17 11:03:45 WET 2009
A few more seconds is Tue Nov 17 11:03:48 WET 2009
generaciondecodigos@nereida:~/src/groovy/strings$

Casiano Rodríguez León
2010-04-30