1. Información general
En este tutorial, analizaremos más de cerca los distintos tipos de cadenas en Groovy, incluidas las cadenas de comillas simples, dobles, triples y slashy.
También exploraremos el soporte de cadenas de Groovy para caracteres especiales, multilínea, expresiones regulares, escape e interpolación de variables.
2. Mejora de java.lang.String
Probablemente sea bueno comenzar diciendo que, dado que Groovy está basado en Java, tiene todas las capacidades de String de Java, como la concatenación, la API de String y los beneficios inherentes del grupo de constantes de String debido a eso.
Veamos primero cómo Groovy extiende algunos de estos conceptos básicos.
2.1. Concatenación de cadenas
La concatenación de cadenas es solo una combinación de dos cadenas:
def first = 'first' def second = "second" def concatenation = first + second assertEquals('firstsecond', concatenation)
Donde Groovy se basa en esto es con sus otros tipos de cuerdas, que veremos en un momento. Tenga en cuenta que podemos concatenar cada tipo indistintamente.
2.2. Interpolación de cadenas
Ahora, Java ofrece algunas plantillas muy básicas a través de printf , pero Groovy va más allá, ofreciendo interpolación de cadenas, el proceso de creación de plantillas de cadenas con variables :
def name = "Kacper" def result = "Hello ${name}!" assertEquals("Hello Kacper!", result.toString())
Si bien Groovy admite la concatenación para todos sus tipos de cadenas, solo proporciona interpolación para ciertos tipos.
2.3. GString
Pero oculto en este ejemplo hay una pequeña arruga: ¿por qué llamamos a toString () ?
En realidad, el resultado no es de tipo String , incluso si lo parece.
Debido a que la clase String es final , la clase string de Groovy que admite la interpolación, GString , no la subclasifica. En otras palabras, para que Groovy proporcione esta mejora, tiene su propia clase de cadena, GString , que no puede extenderse desde String.
En pocas palabras, si lo hiciéramos:
assertEquals("Hello Kacper!", result)
esto invoca assertEquals (Object, Object), y obtenemos:
java.lang.AssertionError: expected: java.lang.String but was: org.codehaus.groovy.runtime.GStringImpl Expected :java.lang.String Actual :org.codehaus.groovy.runtime.GStringImpl
3. Cadena de comillas simples
Probablemente la cadena más simple en Groovy es una con comillas simples:
def example = 'Hello world'
Debajo del capó, estas son simplemente cadenas de Java antiguas , y son útiles cuando necesitamos tener comillas dentro de nuestra cadena.
En vez de:
def hardToRead = "Kacper loves \"Lord of the Rings\""
Podemos concatenar fácilmente una cadena con otra:
def easyToRead = 'Kacper loves "Lord of the Rings"'
Debido a que podemos intercambiar tipos de citas como este, reduce la necesidad de escapar de las comillas.
4. Cadena triple de cotización única
Una cadena triple de comillas simples es útil en el contexto de la definición de contenido de varias líneas.
Por ejemplo, digamos que tenemos algo de JSON para representar como una cadena:
{ "name": "John", "age": 20, "birthDate": null }
No necesitamos recurrir a la concatenación y a los caracteres de nueva línea explícitos para representar esto.
En su lugar, usemos una cadena triple entre comillas simples:
def jsonContent = ''' { "name": "John", "age": 20, "birthDate": null } '''
Groovy almacena esto como una cadena Java simple y agrega la concatenación y las nuevas líneas necesarias para nosotros.
Sin embargo, todavía hay un desafío que superar.
Por lo general, para la legibilidad del código, sangramos nuestro código:
def triple = ''' firstline secondline '''
Pero las cadenas triples de comillas simples conservan los espacios en blanco . Esto significa que la cadena anterior es realmente:
(newline) firstline(newline) secondline(newline)
no:
1 2 |
firstline(newline)
secondline(newline) |
como quizás pretendíamos.
Stay tuned to see how we get rid of them.
4.1. Newline Character
Let's confirm that our previous string starts with a newline character:
assertTrue(triple.startsWith("\n"))
It's possible to strip that character. To prevent this, we need to put a single backslash \ as a first and last character:
def triple = '''\ firstline secondline '''
Now, we at least have:
1 2 |
firstline(newline)
secondline(newline) |
One problem down, one more to go.
4.2. Strip the Code Indentation
Next, let's take care of the indentation. We want to keep our formatting, but remove unnecessary whitespace characters.
The Groovy String API comes to the rescue!
To remove leading spaces on every line of our string, we can use one of the Groovy default methods, String#stripIndent():
def triple = '''\ firstline secondline'''.stripIndent() assertEquals("firstline\nsecondline", triple)
Please note, that by moving the ticks up a line, we've also removed a trailing newline character.
4.3. Relative Indentation
We should remember that stripIndent is not called stripWhitespace.
stripIndent determines the amount of indentation from the shortened, non-whitespace line in the string.
So, let's change the indentation quite a bit for our triple variable:
class TripleSingleQuotedString { @Test void 'triple single quoted with multiline string with last line with only whitespaces'() { def triple = '''\ firstline secondline\ '''.stripIndent() // ... use triple } }
Printing triple would show us:
firstline secondline
Since firstline is the least-indented non-whitespace line, it becomes zero-indented with secondline still indented relative to it.
Note also that this time, we are removing the trailing whitespace with a slash, like we saw earlier.
4.4. Strip with stripMargin()
For even more control, we can tell Groovy right where to start the line by using a | and stripMargin:
def triple = '''\ |firstline |secondline'''.stripMargin()
Which would display:
firstline secondline
The pipe states where that line of the string really starts.
Also, we can pass a Character or CharSequence as an argument to stripMargin with our custom delimiter character.
Great, we got rid of all unnecessary whitespace, and our string contains only what we want!
4.5. Escaping Special Characters
With all the upsides of the triple single-quote string, there is a natural consequence of needing to escape single quotes and backslashes that are part of our string.
To represent special characters, we also need to escape them with a backslash. The most common special characters are a newline (\n) and tabulation (\t).
For example:
def specialCharacters = '''hello \'John\'. This is backslash - \\ \nSecond line starts here'''
will result in:
hello 'John'. This is backslash - \ Second line starts here
There are a few we need to remember, namely:
- \t – tabulation
- \n – newline
- \b – backspace
- \r – carriage return
- \\ – backslash
- \f – formfeed
- \' – single quote
5. Double-Quoted String
While double-quoted strings are also just Java Strings, their special power is interpolation. When a double-quoted string contains interpolation characters, Groovy switches out the Java String for a GString.
5.1.GString and Lazy Evaluation
We can interpolate a double-quoted string by surrounding expressions with ${} or with $ for dotted expressions.
Its evaluation is lazy, though – it won't be converted to a String until it is passed to a method that requires a String:
def string = "example" def stringWithExpression = "example${2}" assertTrue(string instanceof String) assertTrue(stringWithExpression instanceof GString) assertTrue(stringWithExpression.toString() instanceof String)
5.2. Placeholder with Reference to a Variable
The first thing we probably want to do with interpolation is send it a variable reference:
def name = "John" def helloName = "Hello $name!" assertEquals("Hello John!", helloName.toString())
5.2. Placeholder with an Expression
But, we can also give it expressions:
def result = "result is ${2 * 2}" assertEquals("result is 4", result.toString())
We can put even statements into placeholders, but it's considered as bad practice.
5.3. Placeholders with the Dot Operator
We can even walk object hierarchies in our strings:
def person = [name: 'John'] def myNameIs = "I'm $person.name, and you?" assertEquals("I'm John, and you?", myNameIs.toString())
With getters, Groovy can usually infer the property name.
But if we call a method directly, we'll need to use ${}because of the parentheses:
def name = 'John' def result = "Uppercase name: ${name.toUpperCase()}".toString() assertEquals("Uppercase name: JOHN", result)
5.4. hashCode in GString and String
Interpolated strings are certainly godsends in comparison to plain java.util.String, but they differ in an important way.
See, Java Strings are immutable, and so calling hashCode on a given string always returns the same value.
But, GString hashcodes can vary since the String representation depends on the interpolated values.
And actually, even for the same resulting string, they won't have the same hash codes:
def string = "2+2 is 4" def gstring = "2+2 is ${4}" assertTrue(string.hashCode() != gstring.hashCode())
Thus, we should never use GString as a key in a Map!
6. Triple Double-Quote String
So, we've seen triple single-quote strings, and we've seen double-quoted strings.
Let's combine the power of both to get the best of both worlds – multi-line string interpolation:
def name = "John" def multiLine = """ I'm $name. "This is quotation from 'War and Peace'" """
Also, notice that we didn't have to escape single or double-quotes!
7. Slashy String
Now, let's say that we are doing something with a regular expression, and we are thus escaping backslashes all over the place:
def pattern = "\\d{1,3}\\s\\w+\\s\\w+\\\\\\w+"
It's clearly a mess.
To help with this, Groovy supports regex natively via slashy strings:
def pattern = /\d{3}\s\w+\s\w+\\\w+/ assertTrue("3 Blind Mice\Men".matches(pattern))
Slashy strings may be both interpolated and multi-line:
def name = 'John' def example = / Dear ([A-Z]+), Love, $name /
Of course, we have to escape forward slashes:
def pattern = /.*foobar.*\/hello.*/
And we can't represent an empty string with Slashy Stringsince the compiler understands // as a comment:
// if ('' == //) { // println("I can't compile") // }
8. Dollar-Slashy String
Slashy strings are great, though it's a bummer to have to escape the forward slash. To avoid additional escaping of a forward slash, we can use a dollar-slashy string.
Let's assume that we have a regex pattern: [0-3]+/[0-3]+. It's a good candidate for dollar-slashy string because in a slashy string, we would have to write: [0-3]+//[0-3]+.
Dollar-slashy strings are multiline GStrings that open with $/ and close with /$. To escape a dollar or forward slash, we can precede it with the dollar sign ($), but it's not necessary.
We don't need to escape $ in GString placeholder.
For example:
def name = "John" def dollarSlashy = $/ Hello $name!, I can show you a $ sign or an escaped dollar sign: $$ Both slashes work: \ or /, but we can still escape it: $/ We have to escape opening and closing delimiters: - $$$/ - $/$$ /$
would output:
Hello John!, I can show you a $ sign or an escaped dollar sign: $ Both slashes work: \ or /, but we can still escape it: / We have to escape opening and closing delimiter: - $/ - /$
9. Character
Those familiar with Java have already wondered what Groovy did with characters since it uses single quotes for strings.
Actually, Groovy doesn't have an explicit character literal.
There are three ways to make a Groovy string an actual character:
- explicit use of ‘char' keyword when declaring a variable
- using ‘as' operator
- by casting to ‘char'
Let's take a look at them all:
char a = 'A' char b = 'B' as char char c = (char) 'C' assertTrue(a instanceof Character) assertTrue(b instanceof Character) assertTrue(c instanceof Character)
The first way is very convenient when we want to keep the character as a variable. The other two methods are more interesting when we want to pass a character as an argument to a function.
10. Summary
Obviamente, eso fue mucho, así que resumamos rápidamente algunos puntos clave:
- las cadenas creadas con una comilla simple (') no admiten la interpolación
- Las cadenas de comillas dobles triples y oblicuas pueden ser de varias líneas
- Las cadenas de varias líneas contienen caracteres de espacio en blanco debido a la sangría del código
- La barra invertida (\) se usa para escapar de los caracteres especiales en todos los tipos, excepto la cadena de barra inclinada dólar, donde debemos usar dólar ($) para escapar
11. Conclusión
En este artículo, discutimos muchas formas de crear una cadena en Groovy y su soporte para múltiples líneas, interpolación y expresiones regulares.
Todos estos fragmentos están disponibles en Github.
Y para obtener más información sobre las características del lenguaje Groovy en sí, comience bien con nuestra introducción a Groovy.