10 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Строки | String как неизменяемый тип, полезные методы, StringBuilder и buildString |
|
String - неизменяемый тип. Любая «модификация» создаёт новую строку.
val s = "Kotlin"
println(s.length) // 6
println(s[0]) // K — доступ по индексу, как у массива
println(s.uppercase()) // KOTLIN
println(s) // Kotlin — исходная строка не изменилась
На JVM это тот же java.lang.String, со всем его поведением: пул строк, интернирование, представление в LATIN-1 или UTF-16.
Сравнение
val a = "Kotlin"
val b = StringBuilder("Kotlin").toString()
println(a == b) // true — сравнение содержимого
println(a === b) // false — разные объекты
println(a.equals("KOTLIN", ignoreCase = true)) // true
== вызывает equals(), поэтому работает правильно всегда. Это главное практическое отличие от Java, где == на строках сравнивает ссылки.
Для сортировки и порядка - compareTo:
println("abc" < "abd") // true
println("a".compareTo("b")) // -1
println(listOf("b", "a", "c").sorted()) // [a, b, c]
Итерация
Строка - последовательность Char:
for (c in "abc") print("$c.") // a.b.c.
println("abc".toList()) // [a, b, c]
println("hello".count { it == 'l' }) // 2
println("hello".filter { it != 'l' }) // heo
println("abc".map { it.uppercaseChar() }) // [A, B, C]
println("abc".reversed()) // cba
Работают все операции над коллекциями: filter, map, any, count. Только filter и reversed возвращают String, а map - List.
Поиск и проверки
val s = "Hello, Kotlin"
println(s.contains("Kotlin")) // true
println("Kotlin" in s) // true — то же самое
println(s.startsWith("Hello")) // true
println(s.endsWith("lin")) // true
println(s.indexOf("o")) // 4 — первое вхождение, -1 если нет
println(s.lastIndexOf("o")) // 8
println(s.isEmpty()) // false — длина 0
println(" ".isBlank()) // true — пусто или только пробелы
println(s.isNotEmpty()) // true
Для nullable-строк есть безопасные версии, их можно звать без ?.:
val n: String? = null
println(n.isNullOrEmpty()) // true
println(n.isNullOrBlank()) // true
println(n.orEmpty()) // "" — пустая строка вместо null
Извлечение частей
val s = "Hello, Kotlin"
println(s.substring(7)) // Kotlin
println(s.substring(0, 5)) // Hello — [0, 5)
println(s.take(5)) // Hello — первые 5
println(s.takeLast(6)) // Kotlin
println(s.drop(7)) // Kotlin — отбросить первые 7
println(s.dropLast(7)) // Hello
println(s.first()) // H
println(s.last()) // n
println(s.getOrNull(100)) // null — вместо исключения
take и drop безопасны: если запросить больше длины, вернётся вся строка, а не исключение. substring за границами падает.
println("abc".take(100)) // abc
// println("abc".substring(100)) // StringIndexOutOfBoundsException
Изменение
Функции capitalize() и decapitalize() устарели и удалены. Замена пишется так:
fun String.capitalize2() =
replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
fun main() { println("hello".capitalize2()) } // Hello
Остальные методы возвращают новую строку:
fun String.capitalize2() =
replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
val s = " Hello, Kotlin "
println(s.trim()) // "Hello, Kotlin" — с обеих сторон
println(s.trimStart()) // "Hello, Kotlin "
println(s.trimEnd()) // " Hello, Kotlin"
println("Hello".replace("l", "L")) // HeLLo — все вхождения
println("Hello".replaceFirst("l", "L")) // HeLlo
println("Hello".lowercase()) // hello
println("hello".uppercase()) // HELLO
println("hello".capitalize2()) // Hello
println("ab".repeat(3)) // ababab
println("5".padStart(3, '0')) // 005
println("5".padEnd(3)) // "5 "
Разбиение и сборка
println("a,b,c".split(",")) // [a, b, c]
println("a1b2c".split("1", "2")) // [a, b, c] — несколько разделителей
println("a,b,c".split(",", limit = 2)) // [a, b,c]
println("a\nb".lines()) // [a, b] — по переводам строк
println(listOf("a","b").joinToString("-")) // a-b
println(listOf(1,2,3).joinToString(
separator = ", ", prefix = "[", postfix = "]")) // [1, 2, 3]
println("hello".chunked(2)) // [he, ll, o]
joinToString - основной способ собрать строку из коллекции, у него есть и transform:
println(listOf(1, 2).joinToString { "#$it" }) // #1, #2
Преобразование в число
println("42".toInt()) // 42
println("42".toIntOrNull()) // 42
println("abc".toIntOrNull()) // null — не бросает исключение
println("3.14".toDouble()) // 3.14
println("ff".toInt(16)) // 255 — с основанием
// println("abc".toInt()) // NumberFormatException
В коде, который разбирает внешние данные, используйте только версии *OrNull.
StringBuilder и buildString
Для сборки строки в цикле конкатенация создаёт мусор - на каждой итерации новый объект. StringBuilder меняет один буфер:
val sb = StringBuilder()
for (i in 1..3) sb.append(i).append(",")
println(sb.toString()) // 1,2,3,
Идиоматичнее - buildString, он сам создаёт билдер и возвращает результат:
val s = buildString {
append("Привет")
append(", ")
append("Kotlin")
appendLine("!")
append("Строка 2")
}
println(s)
Полезные методы билдера: append, appendLine, insert(i, x), deleteAt(i), setLength(n), reverse(), clear().
Многострочные строки
val json = """
{"name": "Kotlin"}
""".trimIndent()
Подробнее: Строковые шаблоны и вывод
[!info] Отличия от Java
Java Kotlin s.equals(t)s == ts == t(ссылки)s === ts.charAt(0)s[0]s.isEmpty()s.isEmpty(), плюсisBlank(),isNullOrBlank()s.toUpperCase()s.uppercase()new StringBuilder()+toString()buildString { }String.join(",", list)list.joinToString(",")Integer.parseInt(s)s.toInt()/s.toIntOrNull()s.split(",")→String[]→ List<String>s.substring(100)падаетs.take(100)безопасенГлавное приобретение -
*OrNull-функции и безопасныеtake/dropвместо исключений на каждой опечатке во входных данных.
uppercase()вместоtoUpperCase()- не просто переименование: старая версия Java зависела от системной локали, из-за чего"i".toUpperCase()в турецкой локали давалоİ. Kotlin по умолчанию используетLocale.ROOT.Сравнить: Строки в Java
[!warning] Подводные камни
lengthсчитает кодовые единицы UTF-16, а не символы. Эмодзи и редкие иероглифы занимают две:println("😀".length) // 2, а не 1 println("😀".count()) // 2 — то же самое println("😀".codePointCount(0, "😀".length)) // 1 — настоящее число символовРазрезать такую строку по индексу - значит получить битые символы.
splitс пустым разделителем и пустой строкой дают неожиданный результат:println("".split(",")) // [] — один пустой элемент, размер 1 println("".split(",").size) // 1 println("a,,b".split(",")) // [a, , b] — пустой элемент сохраняется
trimIndentне обрезает пробелы внутри строк - только общий отступ.
replaceпринимает строку, а не regex - в отличие от Java:println("a.b".replace(".", "-")) // a-b — точка как символ println("a.b".replace(Regex("."), "-")) // --- — regexВ Java
String.replaceAllработает с regex, и это частый источник путаницы при переходе.Строка в
whenсравнивается по значению, но полнота не проверяется - дляStringвсегда нуженelse.
Дополнительно: Char Далее: Char