Перейти к основному содержимому

Типы строк в Groovy

· 11 мин. чтения

Задача: Наибольшая подстрока без повторений

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

ANDROMEDA 42

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 является final , строковый класс 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 Strings , и они пригодятся , когда нам нужно иметь кавычки внутри нашей строки.

Вместо:

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)

нет:

firstline(newline)
secondline(newline)

как, возможно, мы и намеревались.

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

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

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

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

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

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

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

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. Разделить с помощью stripMargin()

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

def triple = '''\
|firstline
|secondline'''.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 – возврат
  • \r – возврат каретки
  • \\ — обратная косая черта
  • \f – перевод страницы
  • \' – одинарная кавычка

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

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

5.1. GString и ленивые вычисления

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

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

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. Косая нить

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

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

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

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

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

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

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

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

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

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

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

8. Долларовая полоса

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

Предположим, что у нас есть шаблон регулярного выражения: [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 .