Регулярные выражения в Kotlin с примерами: на практике
Регулярные выражения (regex) — мощный инструмент для работы со строками. В Kotlin они реализованы на основе Java-классов Pattern и Matcher, но при этом язык добавляет собственные удобные расширения и синтаксический сахар. В этой статье мы разберем, как использовать регулярные выражения в Kotlin на реальных примерах: от простого поиска до замены и разбиения строк.
Основы синтаксиса: создание и компиляция
В Kotlin регулярное выражение можно создать двумя способами: через класс Regex или с помощью строки с шаблоном. Первый способ предпочтительнее, так как предоставляет больше методов.
// Способ 1: через класс Regex
val regex = Regex("\\d+") // одна или более цифр
// Способ 2: через строку (не рекомендуется для сложных выражений)
val pattern = "\\d+".toRegex()
Обратите внимание на двойной экранирующий слэш: \\d — это экранирование для Java/Kotlin, чтобы получить символ \d в строке. Для упрощения Kotlin поддерживает raw строки (тройные кавычки """), где экранирование не требуется:
val regex = Regex("""\d+""") // raw строка — обратные слэши не нужно дублировать
Также можно добавить флаги, например, RegexOption.IGNORE_CASE:
val regex = Regex("kotlin", RegexOption.IGNORE_CASE)
Поиск и проверка совпадений
Самые частые операции — проверить, содержит ли строка подстроку по шаблону, и найти все совпадения.
Проверка наличия совпадения: containsMatchIn
val text = "Мой номер: 123-45-678"
val digitRegex = Regex("""\d+""")
if (digitRegex.containsMatchIn(text)) {
println("В тексте есть цифры!")
}
Поиск первого совпадения: find
val result = digitRegex.find(text)
println(result?.value) // Выведет: 123
Поиск всех совпадений: findAll
val allMatches = digitRegex.findAll(text)
allMatches.forEach { match ->
println("Найдено: ${match.value}") // 123, 45, 678
}
Метод findAll возвращает последовательность объектов MatchResult. Каждый такой объект содержит не только само совпадение (.value), но и его позицию в строке (.range).
Работа с группами захвата
Группы захвата — это части шаблона, заключенные в круглые скобки. Они позволяют извлекать конкретные фрагменты данных. Например, разберем email на имя пользователя и домен:
val email = "user@example.com"
val emailRegex = Regex("""(.+)@(.+)""")
val matchResult = emailRegex.find(email)
if (matchResult != null) {
val (username, domain) = matchResult.destructured
println("Имя пользователя: $username") // user
println("Домен: $domain") // example.com
}
Конструкция destructured удобно разбивает группы на переменные. Если групп больше, можно обращаться по индексу:
val fullMatch = matchResult.value // user@example.com
val group1 = matchResult.groupValues[1] // user
val group2 = matchResult.groupValues[2] // example.com
Именованные группы (Kotlin 1.7+)
Начиная с Kotlin 1.7, можно использовать именованные группы:
val namedRegex = Regex("""(?<name>\w+)@(?<domain>\w+\.\w+)""")
val match = namedRegex.find("contact@kotlin.org")
if (match != null) {
println(match.groups["name"]?.value) // contact
println(match.groups["domain"]?.value) // kotlin.org
}
Замена текста с помощью replace
Метод replace позволяет заменить все совпадения на новую строку. В шаблоне замены можно ссылаться на группы захвата через $1, $2 и т.д.
val phone = "+7 (123) 456-78-90"
val cleanRegex = Regex("""[^\d+]""") // всё, кроме цифр и +
val cleaned = cleanRegex.replace(phone, "")
println(cleaned) // +71234567890
Пример замены с перестановкой групп (меняем формат даты):
val date = "2024-01-15"
val dateRegex = Regex("""(\d{4})-(\d{2})-(\d{2})""")
val newFormat = dateRegex.replace(date, "$3.$2.$1")
println(newFormat) // 15.01.2024
Для более сложной логики можно передать лямбду:
val text = "Цены: 100 руб, 250 руб, 30 руб"
val priceRegex = Regex("""\d+""")
val result = priceRegex.replace(text) { match ->
val value = match.value.toInt()
(value * 1.2).toInt().toString() // увеличиваем на 20%
}
println(result) // Цены: 120 руб, 300 руб, 36 руб
Разбиение строки: split
Метод split работает аналогично String.split, но принимает регулярное выражение:
val data = "apple,banana;cherry|date"
val splitRegex = Regex("""[,;|]""")
val fruits = splitRegex.split(data)
println(fruits) // [apple, banana, cherry, date]
Если нужно разбить только по первому совпадению, используйте splitToSequence с ограничением:
val limited = splitRegex.split(data, 2)
println(limited) // [apple, banana;cherry|date]
Практический пример: парсинг логов
Предположим, у нас есть строка лога сервера:
val log = "2024-03-15 14:30:00 ERROR: User 'alex' failed login from IP 192.168.1.1"
val logRegex = Regex(
"""(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (INFO|ERROR|WARN): User '(\w+)' .*? IP (\