Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Строки

Пробежаться по строке
Конвертируем строку в число
Дополняем строку символами
replace()/replaceRange()
Конвертируем строку в дату
StringBuilder

Со строками в Kotlin работать стало проще.

Поздороваемся с котом по имени Барсик.


val catName: String = "Барсик"
println("Привет $catName! Как дела?")

Обратите внимание, что мы добавили знак доллара к имени переменной и используем её прямо в строке. В Java нам пришлось бы разбивать строку и соединять её с помощью конкатенации.


// Java
println("Привет " + catName + "! Как дела?");

Кстати, такой код тоже будет работать, но первый способ гораздо удобнее.

Мы понимаем, что речь идёт о строке, поэтому можно было написать код без указания типа.


val catName = "Барсик"

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


println("Привет \$catName! Как дела?")
// выводится: Привет $catName! Как дела?

Мы использовали так называемый "строковый шаблон". За кулисами происходит соединение при помощи StringBuilder, но более удобным и быстрым способом.

Длину строки можно вычислить через функцию count().


val murzik = "Мурзик"
println(murzik.count())

Функции-расширения

Разработчики JetBrains добавили множество готовых функций-расширений для многих классов, в том числе и для строк. Найти их можно в файле String.kt (в студии дважды нажмите клавишу Shift и в поисковой строке наберите имя данного файла для просмотра исходника).

Некоторые примеры функций-расширений представлены ниже. На самом деле их больше, изучайте самостоятельно.

Пробежаться по строке

Строку можно рассматривать как массив символов.


val cat = "Барсик"
val character = cat[2]
println(character) // выводит р

Пробежаться по всей строке без использования индекса.


val cat = "Барсик"

for(char in cat){
    println(char)
}

Пробежаться по всей строки с использованием индекса.


val cat = "Барсик"

for (char in cat.indices){
    print(cat[char] + "\n")
}

Саму строку можно предварительно явно перевести в массив символов.


for(char in cat.toCharArray()){
    println(char)
}

Можно вызывать forEach.


cat.forEach { char -> println(char) }

Если вам нужен не только символ строки, но и его индекс, то вызываем forEachIndexed.


cat.forEachIndexed{index, char -> println("Index $index Character $char")}

// Результат
Index 0 Character Б
Index 1 Character а
Index 2 Character р
Index 3 Character с
Index 4 Character и
Index 5 Character к

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


val cat = "Барсик"
val index = 5
println("Character at index $index in $cat is ${cat[index]}")

Если вы укажете несуществующий индекс, то получите исключение StringIndexOutOfBoundsException, поэтому делайте проверку.

Рассмотрим пример с числами. Допустим нужно произвести арифметические действия с целыми числами и вывести результат в строке.


val a: Int = 9
val b: Int = 1

fun main(args: Array<String>) {
    println("Осталось жизней: ${a - b}")
}

Снова используем знак доллара, а действия с числами поместили в фигурные скобки. Kotlin сама разберётся с типами данных и сделает всю работу.

Выведем второй элемент массива.


fun main(args: Array<String>) {
    val names = arrayListOf("Мурзик")
    names.add("Васька")
    names.add("Барсик")
    
    println("Кота зовут ${names[0]}")
}

Опять используем знак доллара и фигурные скобки.

Можно даже использовать условие.


val count = 9
print("value of count is ${if (count == 10) "equal to 10" else "not equal to 10"}")

Многострочный текст можно создать, используя тройные кавычки. Больше нам не нужны символы перевода строки \n, а также символы типа \t, \b, \r и т.д.


val multipleStringLines = """
    У лукоморья дуб зелёный;
    Златая цепь на дубе том:
    И днём и ночью кот учёный
    Всё ходит по цепи кругом;
    Идёт направо - песнь заводит,
    Налево - сказку говорит. """

Метод trimMargin() позволяет убрать "пустые" символы из текста по разделителю | (по умолчанию):


val myString = """This is the first line
|This is the second line
    |This is the third line
        |And fourth line
"""
println(myString.trimMargin())

На выходе получим.


This is the first line
This is the second line
This is the third line
And fourth line

Можно задать свой собственный разделитель и указать его в методе.


trimMargin(">")

Если первая и последняя строки длинного предложения пустые, то они будут удалены и строки.

Можем делать со строкой знакомые операции в удобной обёртке.


val cat = "Барсик"
println(cat.reversed()) // кисраБ
println(cat.takeLast(2)) // ик

val kitty = "murzik@gmail.com"
println(kitty.substringBefore("@")) // murzik

Конвертируем строку в число

Сконвертировать строку в число можно через соответствующие методы.


val str = "123"
print(str.toLong())

Если нужно обработать ситуацию с null, то используйте другой метод.


val str = "123.4"
println(str.toLongOrNull()) // вернёт null

По умолчанию мы подразумеваем десятичное счисление. Но возможна конвертация в другой системе счисления - двоичной, восьмеричной и т.д.


val str = "11111111"
print(str.toLongOrNull(2)) //255

val str = "105"
print(str.toLongOrNull(8)) //69

Также есть схожие методы toShort(), toShortOrNull(), toInt(), toIntOrNull(), toFloat(), toDouble() и т.д. Часть из них поддерживает перегруженные версии с другой системой счисления, но проверяйте поддерживаемые системы.

Не совсем про числа, но можно сконвертировать в булево значение.


val str = "false"
println(str.toBoolean())

Дополняем строку символами

Можно дополнить строку символами с самого начала или в конце.


val name = "Barsik"
val pad = name.padStart(10, '#')
println(pad) // ####Barsik

val name = "Barsik"
val pad = name.padEnd(10, '*')
println(pad) // Barsik****

По умолчанию символом дополнения является пробел, поэтому можно вызывать методы без второго параметра, чтобы получить строку с пробелами.

Подстроки


// подстрока с указанного индекса
val result = "developer.alexanderklimov.ru".substring(10) // alexanderklimov.ru

// подстрока до первого указанного разделителя
val first = "developer.alexanderklimov.ru".substringBefore('.') // developer

// подстрока после первого указанного разделителя
val last = "developer.alexanderklimov.ru".substringAfter('.') // alexanderklimov.ru

// подстрока после последнего указанного разделителя
val last = "developer.alexanderklimov.ru".substringAfterLast('.') // ru

// подстрока до последнего указанного разделителя
val beforeLast = "developer.alexanderklimov.ru".substringBeforeLast('.') // developer.alexanderklimov

Ещё пример для получения адреса сайта без http://.


val superSite = "http://developer.alexanderklimov.ru"
val index = superSite.lastIndexOf('/')
println("Индекс нужного нам символа: $index")

println(superSite.substring(index + 1)) // developer.alexanderklimov.ru

// другой вариант
println(superSite.substringAfterLast("/"))

Встроенные функции

Kotlin содержит множество встроенных удобных функций для работы со строками. Часть из них уже использовалась в примерах выше. Упомянем ещё несколько полезных функций.


val blank = "   ".isBlank() // true, если пустая строка или пустые символы пробела, табуляции и т.п.

// индекс последнего символа
val lastIndex = "Кот Мурзик".lastIndex // 9

// переводим в верхний регистр первый символ строки
// decapitalize() выполняем обратную задачу
val capitalize = "кот Мурзик".capitalize()

val withSpaces = "1".padStart(2) // добавляем пробел перед строкой
val endZeros = "1".padEnd(3, '0') // "100"  добавляем нули в конец

val dropStart = "Kotlin".drop(2) // "tlin" убираем первые символы в указанном количестве
val dropEnd = "Kotlin".dropLast(3) // "Kot" убираем последние символы в указанном количестве

// возвращаем строку без первого символа, который удовлетворяет условию
val string = "Мурзик"
val result = string.dropWhile{
    it == 'М'
}
textView.text = result // урзик

// возвращаем строку без последнего символа, который удовлетворяет условию
val string = "Мурзик"
val result = string.dropLastWhile{
    it == 'к'
}
textView.text = result // Мурзи

// разбиваем на массив строк
"A\nB\nC".lines() // [A, B, C]
"ABCD".zipWithNext() // [(A, B), (B, C), (C, D)]

// удаляем символы из заданного диапазона
val string = "Кот, который гулял сам по себе"
val result = string.removeRange(
    3..28 // range
)

// Функции removeXXX() хороши для строк в виде ##Cat##, чтобы убрать лишние символы

// удаляем префикс из строки
val string = "Кот, который гулял сам по себе"
val result = string.removePrefix("Кот")

// удаляем суффикс из строки
val string = "Кот, который гулял сам по себе"
val result = string.removeSuffix("себе")

// удаляем заданный разделитель, который должен окружать строку с начала и с конца
val string = "та, тра-та-та, мы везём с собой кота"

val result = string.removeSurrounding(
    "та" // delimiter
)

textView.text = result // , тра-та-та, мы везём с собой ко

// Также можно указать разные начало и конец, которые окружают строку
val string = "Тра-та-та, тра-та-та, мы везём с собой кота"

val result = string.removeSurrounding(
    "Тра-", // prefix
    " кота" // suffix
)

textView.text = result // та-та, тра-та-та, мы везём с собой


// Добавляем отступы при явном переводе на новую строку
val string = "Какой-то длинный текст, \nсостоящий из имён котов: " +
        "\nВаська" +
        "\nБарсик" +
        "\nРыжик"

val result = string.prependIndent(
    "     " // indent
)

// Разбиваем символы на две группы. 
// В первую группу попадут символы в верхнем регистре, во вторую - символы в нижнем регистре
val string = "Кот Васька и кот Мурзик - Друзья!"

val result: Pair<String, String> = string.partition {
    it.isUpperCase()
}

textView.text = result.first + ":" + result.second //КВМД:от аська и кот урзик - рузья!

// Разбиваем строку на список строк. В качестве разделителя - перевод на новую строку
val string = "Кот Васька\nКотМурзик\nКот Мурзик"

// Split string into lines (CRLF, LF or CR)
val lines: List<String> = string.lines()

textView.text = "Кол-во строк: ${lines.size}"
lines.forEach {
    textView.append("\n" + it)
}

// Разбиваем строку на список строк с указанным числом символов. В последней строке может выводиться остаток
val string = "Тра-та-та, тра-та-та, мы везём с собой кота"
val list:List<String> = string.chunked(11)
list.forEach {
    textView.append("$it\n")
}

/*
Тра-та-та, 
тра-та-та, 
мы везём с 
собой кота
*/

// Содержит ли строка только цифры (используем предикат)
val string = "09032020"

// Returns true if all characters match the given predicate.
val result: Boolean = string.all{
    it.isDigit()
}
textView.append("Is the string $string contain only digit? $result")

// Содержит ли строка хотя бы одну цифру (используем предикат)
val string = "3 кота"
// Returns true if at least one character matches the given predicate.
val result: Boolean = string.any() {
    it.isDigit()
}
textView.append("Is the text \"$string\" contain any digit? $result")

// Сравниваем две строки с учётом регистра
val string1 = "This is a sample string."
val string2 = "This is a SAMPLE string."

if (string1.compareTo(string2, ignoreCase = true) == 0) {
    textView.append("\n\nBoth strings are equal, ignoring case.")
} else {
    textView.append("\n\nBoth strings are not equal, ignoring case.")
}

Можно фильтровать данные с помощью filter(). Допустим, мы хотим извлечь только цифры из строки.


val string = "9 жизней (2016) - Nine Lives - информация о фильме"
val filteredText = string.filter { it.isDigit() }
textView.text = filteredText // 92016

Если хочется решить обратную задачу и получить только символы, но не цифры - то достаточно вызвать filterNot().


val filteredText = string.filterNot { it.isDigit() }

replace()/replaceRange()

Для замены отдельных символов или строк используется функция replace(). Заменим отдельные символы в строке.


val string = "Кит Кишка"

val result = string.replace(
    'и', // old char
    'о', // new char
    true // ignore case Boolean = false
)

textView.text = result // Кот Кошка

Можно менять подстроки.


val result = string.replace(
    "Собака", // old value
    "Кот", // new value
    true // ignore case
)

textView.text = result // Кот - друг человека

Вариант replace() с регулярным выражением. Создадим функцию перевода строки на "драконий" язык. В результате будет создана новая строка с нужными символами.


private fun toDragonSpeak(phrase: String) =
        phrase.replace(Regex("[aeiou]")) {
            when (it.value) {
                "a" -> "4"
                "e" -> "3"
                "i" -> "1"
                "o" -> "0"
                "u" -> "|_|"
                else -> it.value
            }
        }
		
println(toDragonSpeak("Kitten")) // K1tt3n

Можно заменять подстроки через replaceRange(), указывая нужный диапазон. Существуют две версии этого способа.


val string = "Тра-та-та, тра-та-та, мы везём с собой ежа"

// The end index of the range is included
// in the part to be replaced.
val result = string.replaceRange(
    39..40, // range
    "кот" // replacement
)

textView.append(result)

val result2 = string.replaceRange(
    39, // start index
    41, // end index
    "кот" // replacement
)

textView.append("\n")
textView.append(result2)

Конвертируем строку в дату


import java.time.LocalDate

var parsedDate = LocalDate.parse("2020-07-27")
println(parsedDate)

Есть также вариант с использованием DateTimeFormatter.


import java.time.LocalDate
import java.time.format.DateTimeFormatter

var parsedDate = LocalDate.parse("Wednesday, July 27, 2020", DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy"))
println("Wednesday, July 27, 2020 : " + parsedDate)

parsedDate = LocalDate.parse("July 27, 2020", DateTimeFormatter.ofPattern("MMMM dd, yyyy"))
println("July 27, 2020 : " + parsedDate)

parsedDate = LocalDate.parse("14/02/2020", DateTimeFormatter.ofPattern("dd/MM/yyyy"))
println("14/02/2020 : "+parsedDate)

parsedDate = LocalDate.parse("27 July,2019", DateTimeFormatter.ofPattern("dd MMMM,yyyy"))
println("27 July,2019 : " + parsedDate)

parsedDate = LocalDate.parse("11th April,2012", DateTimeFormatter.ofPattern("dd'th' MMMM,yyyy"))
println("11th April,2012 : " + parsedDate)

parsedDate = LocalDate.parse("27 Feb, 2001", DateTimeFormatter.ofPattern("dd MMM, yyyy"))
println("27 Feb, 2001 : " + parsedDate)

StringBuilder

У класса StringBuilder в Kotlin есть отдельная функция buildString(), которая поможет сократить количество кода для получения строки.


fun printAlphabet() = buildString {
    for (letter in 'A'..'Z')
        append(letter)
}

Дополнительные материалы

Упражнения для строк

Реклама