«1. Обзор

В этом руководстве мы подробно рассмотрим несколько типов строк в Groovy, включая строки в одинарных, двойных, тройных кавычках и строки с косой чертой.

Мы также изучим поддержку строк Groovy для специальных символов, многострочных, регулярных выражений, экранирования и интерполяции переменных.

2. Расширение java.lang.String

Вероятно, было бы неплохо начать с утверждения, что, поскольку Groovy основан на Java, он имеет все возможности Java String, такие как конкатенация, String API и неотъемлемые преимущества String. постоянный пул из-за этого.

Давайте сначала посмотрим, как Groovy расширяет некоторые из этих основ.

2.1. Конкатенация строк

Конкатенация строк — это всего лишь комбинация двух строк:

def first = 'first'
def second = "second"        
def concatenation = first + second
assertEquals('firstsecond', concatenation)

Groovy опирается на несколько других типов строк, которые мы рассмотрим чуть позже. Обратите внимание, что мы можем конкатенировать каждый тип взаимозаменяемо.

2.2. Интерполяция строк

Теперь Java предлагает очень простые шаблоны через printf, но Groovy идет глубже, предлагая интерполяцию строк, процесс шаблонов строк с переменными:

def name = "Kacper"
def result = "Hello ${name}!"
assertEquals("Hello Kacper!", result.toString())

Хотя Groovy поддерживает конкатенацию для всех своих типов строк, он обеспечивает интерполяцию только для определенных типов.

2.3. GString

Но в этом примере спрятан небольшой нюанс — зачем мы вызываем toString()?

На самом деле результат не имеет типа String, даже если выглядит так.

Поскольку класс String является окончательным, строковый класс Groovy, поддерживающий интерполяцию, GString, не является его подклассом. Другими словами, чтобы Groovy мог предоставить это усовершенствование, у него есть собственный строковый класс GString, который не может наследоваться от String.

Проще говоря, если бы мы это сделали:

assertEquals("Hello Kacper!", result)

это вызывает assertEquals(Object, Object), и мы получаем:

java.lang.AssertionError: expected: java.lang.String<Hello Kacper!>
  but was: org.codehaus.groovy.runtime.GStringImpl<Hello Kacper!>
Expected :java.lang.String<Hello Kacper!> 
Actual   :org.codehaus.groovy.runtime.GStringImpl<Hello Kacper!>

3. Строка в одинарных кавычках

Вероятно, самая простая строка в Groovy один с одинарными кавычками:

def example = 'Hello world'

Под капотом это просто старые строки Java, и они пригодятся, когда нам нужно иметь кавычки внутри нашей строки.

Вместо:

def hardToRead = "Kacper loves \"Lord of the Rings\""

Мы можем легко соединить одну строку с другой:

def easyToRead = 'Kacper loves "Lord of the Rings"'

Так как мы можем менять типы кавычек таким образом, это уменьшает потребность в экранировании кавычек.

4. Строка с тройными одинарными кавычками

Строка с тройными одинарными кавычками полезна в контексте определения многострочного содержимого.

Например, предположим, что у нас есть некоторый JSON для представления в виде строки:

{
    "name": "John",
    "age": 20,
    "birthDate": null
}

Нам не нужно прибегать к конкатенации и явным символам новой строки для представления этого.

Вместо этого давайте воспользуемся тройной строкой в ​​одинарных кавычках:

def jsonContent = '''
{
    "name": "John",
    "age": 20,
    "birthDate": null
}
'''

Groovy сохраняет ее как простую строку Java и добавляет для нас необходимую конкатенацию и новые строки.

Однако есть еще одна проблема, которую нужно преодолеть.

Обычно для удобочитаемости кода мы делаем отступ:

def triple = '''
    firstline
    secondline
'''

Но строки с тройными одинарными кавычками сохраняют пробелы. Это означает, что приведенная выше строка на самом деле:

(newline)
    firstline(newline)
    secondline(newline)

не:

1
2
    firstline(newline)
    secondline(newline)

как, возможно, мы и предполагали.

Оставайтесь с нами, чтобы узнать, как мы избавимся от них.

4.1. Символ новой строки

Давайте подтвердим, что наша предыдущая строка начинается с символа новой строки:

assertTrue(triple.startsWith("\n"))

Этот символ можно удалить. Чтобы предотвратить это, нам нужно поставить по одной обратной косой черте \\ в качестве первого и последнего символа:

def triple = '''\
    firstline
    secondline
'''

Теперь у нас по крайней мере есть:

1
2
    firstline(newline)
    secondline(newline)

Одна проблема решена, еще одна остается.

4.2. Убираем отступы в коде

Теперь давайте позаботимся об отступах. Мы хотим сохранить наше форматирование, но удалить ненужные пробельные символы.

Groovy String API приходит на помощь!

Чтобы удалить начальные пробелы в каждой строке нашей строки, мы можем использовать один из методов Groovy по умолчанию, String#stripIndent():

def triple = '''\
    firstline
    secondline'''.stripIndent()
assertEquals("firstline\nsecondline", triple)

Обратите внимание, что, перемещая галочки вверх по строке, мы также удален завершающий символ новой строки.

4.3. Относительный отступ

Мы должны помнить, что stripIndent не называется stripWhitespace.

stripIndent определяет величину отступа от укороченной строки без пробелов в строке.

Итак, давайте немного изменим отступ для нашей тройной переменной:

class TripleSingleQuotedString {

    @Test
    void 'triple single quoted with multiline string with last line with only whitespaces'() {
        def triple = '''\
            firstline
                secondline\
        '''.stripIndent()

        // ... use triple
    }
}

Печать тройной покажет нам:

firstline
    secondline

«

«Поскольку первая строка является строкой без пробелов с наименьшим отступом, она становится с нулевым отступом, а вторая строка все еще имеет отступ относительно нее.

Также обратите внимание, что на этот раз мы удаляем завершающий пробел косой чертой, как мы видели ранее.

4.4. Strip с помощью stripMargin()

def triple = '''\
    |firstline
    |secondline'''.stripMargin()

Для еще большего контроля мы можем указать Groovy, где начинать строку, используя | и stripMargin:

firstline
secondline

Что будет отображать:

Канал указывает, где действительно начинается эта строка строки.

Кроме того, мы можем передать Character или CharSequence в качестве аргумента для stripMargin с нашим настраиваемым символом-разделителем.

Отлично, мы избавились от всех ненужных пробелов, и наша строка содержит только то, что нам нужно!

4.5. Экранирование специальных символов

При всех преимуществах строки с тройными одинарными кавычками естественным следствием является необходимость экранирования одинарных кавычек и обратной косой черты, которые являются частью нашей строки.

Чтобы представить специальные символы, нам также нужно экранировать их с помощью обратной косой черты. Наиболее распространенными специальными символами являются перевод строки (\\n) и табуляция (\\t).

def specialCharacters = '''hello \'John\'. This is backslash - \\ \nSecond line starts here'''

Например:

hello 'John'. This is backslash - \
Second line starts here

приведет к:

    Есть несколько вещей, которые нам нужно запомнить, а именно:

\\t – табуляция \\n – новая строка \\b — backspace \\r — возврат каретки \\\\ — обратная косая черта \\f — перевод страницы \\’ — одинарная кавычка

5. Строка в двойных кавычках

Хотя строки в двойных кавычках также просто Строки Java, их особая сила — интерполяция. Когда строка в двойных кавычках содержит символы интерполяции, Groovy переключает строку Java на строку GString.

5.1. GString и отложенное вычисление

Мы можем интерполировать строку в двойных кавычках, заключая выражения в ${} или $ для выражений с точками.

def string = "example"
def stringWithExpression = "example${2}"
assertTrue(string instanceof String)
assertTrue(stringWithExpression instanceof GString)
assertTrue(stringWithExpression.toString() instanceof String)

Его вычисление ленивое, однако оно не будет преобразовано в строку, пока не будет передано методу, которому требуется строка:

5.2. Заполнитель со ссылкой на переменную

def name = "John"
def helloName = "Hello $name!"
assertEquals("Hello John!", helloName.toString())

Первое, что мы, вероятно, хотим сделать с интерполяцией, это послать ей ссылку на переменную:

5.2. Заполнитель с выражением

def result = "result is ${2 * 2}"    
assertEquals("result is 4", result.toString())

Но мы также можем дать ему выражения:

Мы можем помещать в заполнители даже операторы, но это считается плохой практикой.

5.3. Заполнители с оператором точки

def person = [name: 'John']
def myNameIs = "I'm $person.name, and you?"
assertEquals("I'm John, and you?", myNameIs.toString())

Мы можем даже пройтись по иерархии объектов в наших строках:

С геттерами Groovy обычно может вывести имя свойства.

def name = 'John'
def result = "Uppercase name: ${name.toUpperCase()}".toString()
assertEquals("Uppercase name: JOHN", result)

Но если мы вызовем метод напрямую, нам придется использовать ${} из-за круглых скобок:

5.4. hashCode в GString и String

Интерполированные строки, безусловно, являются находкой по сравнению с простым java.util.String, но они имеют важное отличие.

Видите ли, строки Java неизменяемы, поэтому вызов hashCode для данной строки всегда возвращает одно и то же значение.

Но хэш-коды GString могут различаться, поскольку представление String зависит от интерполированных значений.

def string = "2+2 is 4"
def gstring = "2+2 is ${4}"
assertTrue(string.hashCode() != gstring.hashCode())

И на самом деле, даже для одной и той же результирующей строки они не будут иметь одинаковых хеш-кодов:

Таким образом, мы никогда не должны использовать GString в качестве ключа в карте!

6. Строка с тройными двойными кавычками

Итак, мы видели строки с тройными одинарными кавычками и строки с двойными кавычками.

def name = "John"
def multiLine = """
    I'm $name.
    "This is quotation from 'War and Peace'"
"""

Давайте объединим силу обоих, чтобы получить лучшее из обоих миров – многострочная интерполяция строк:

Также обратите внимание, что нам не нужно избегать одинарных или двойных кавычек!

7. Slashy String

def pattern = "\\d{1,3}\\s\\w+\\s\\w+\\\\\\w+"

Теперь предположим, что мы делаем что-то с регулярным выражением, и, таким образом, мы избегаем обратной косой черты повсюду:

Это явно беспорядок.

def pattern = /\d{3}\s\w+\s\w+\\\w+/
assertTrue("3 Blind Mice\Men".matches(pattern))

Чтобы помочь в этом, Groovy изначально поддерживает регулярные выражения через строки с косой чертой:

def name = 'John'
def example = /
    Dear ([A-Z]+),
    Love, $name
/

Строки с косой чертой могут быть как интерполированными, так и многострочными:

def pattern = /.*foobar.*\/hello.*/

Конечно, мы должны избегать косых черт: ~ ~~

// if ('' == //) {
//     println("I can't compile")
// }

И мы не можем представить пустую строку с Slashy String, так как компилятор понимает // как комментарий:

8. Dollar-Slashy String

Slashy строки хороши, хотя облом, чтобы избежать косой черты. Чтобы избежать дополнительного экранирования косой черты, мы можем использовать строку с косой чертой.

«Предположим, что у нас есть шаблон регулярного выражения: [0-3]+/[0-3]+. Это хороший кандидат на строку с косой чертой, потому что в строке с косой чертой нам пришлось бы писать: [0-3]+//[0-3]+.

Долларовые строки — это многострочные GString, которые начинаются с $/ и закрываются с помощью /$. Чтобы избежать знака доллара или косой черты, перед ним можно поставить знак доллара ($), но это не обязательно.

Нам не нужно экранировать символ $ в заполнителе GString.

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:
    - $$$/  
    - $/$$
 /$

Например:

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. Символ

Те, кто знаком с Java, уже задавались вопросом, что Groovy сделал с символами, поскольку он использует одинарные кавычки для строк.

На самом деле в Groovy нет явного символьного литерала.

    Есть три способа сделать строку Groovy реальным символом:

явное использование ключевого слова «char» при объявлении переменной с использованием оператора «as» путем приведения к «char»

char a = 'A'
char b = 'B' as char
char c = (char) 'C'
assertTrue(a instanceof Character)
assertTrue(b instanceof Character)
assertTrue(c instanceof Character)

Давайте рассмотрим их все:

Первый способ очень удобен, когда мы хотим сохранить символ как переменную. Два других метода более интересны, когда мы хотим передать символ в качестве аргумента функции.

10. Резюме

    Очевидно, что это было много, поэтому давайте быстро суммируем некоторые ключевые моменты: быть многострочными многострочные строки содержат пробельные символы из-за отступа кода Обратная косая черта (\\) используется для экранирования специальных символов в каждом типе, кроме строки с косой чертой доллара, где мы должны использовать доллар ($), чтобы экранировать

11. Заключение

В этой статье мы обсудили множество способов создания строки в Groovy и его поддержку многострочного представления, интерполяции и регулярных выражений.

Все эти фрагменты доступны на Github.

А для получения дополнительной информации о возможностях самого языка Groovy начните с нашего введения в Groovy.