Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,222 @@
---
title: Строки
description: String как неизменяемый тип, полезные методы, StringBuilder и buildString
tags:
- kotlin
- stdlib
---
`String` - неизменяемый тип. Любая «модификация» создаёт новую строку.
```kotlin
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.
## Сравнение
```kotlin
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`:
```kotlin
println("abc" < "abd") // true
println("a".compareTo("b")) // -1
println(listOf("b", "a", "c").sorted()) // [a, b, c]
```
## Итерация
Строка - последовательность `Char`:
```kotlin
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`.
## Поиск и проверки
```kotlin
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-строк есть безопасные версии, их можно звать без `?.`:
```kotlin
val n: String? = null
println(n.isNullOrEmpty()) // true
println(n.isNullOrBlank()) // true
println(n.orEmpty()) // "" — пустая строка вместо null
```
## Извлечение частей
```kotlin
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` за границами падает.
```kotlin
println("abc".take(100)) // abc
// println("abc".substring(100)) // StringIndexOutOfBoundsException
```
## Изменение
Функции `capitalize()` и `decapitalize()` устарели и удалены. Замена пишется так:
```kotlin
fun String.capitalize2() =
replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
fun main() { println("hello".capitalize2()) } // Hello
```
Остальные методы возвращают новую строку:
```kotlin
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 "
```
## Разбиение и сборка
```kotlin
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`:
```kotlin
println(listOf(1, 2).joinToString { "#$it" }) // #1, #2
```
## Преобразование в число
```kotlin
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` меняет один буфер:
```kotlin
val sb = StringBuilder()
for (i in 1..3) sb.append(i).append(",")
println(sb.toString()) // 1,2,3,
```
Идиоматичнее - `buildString`, он сам создаёт билдер и возвращает результат:
```kotlin
val s = buildString {
append("Привет")
append(", ")
append("Kotlin")
appendLine("!")
append("Строка 2")
}
println(s)
```
Полезные методы билдера: `append`, `appendLine`, `insert(i, x)`, `deleteAt(i)`, `setLength(n)`, `reverse()`, `clear()`.
## Многострочные строки
```kotlin
val json = """
{"name": "Kotlin"}
""".trimIndent()
```
Подробнее: [Строковые шаблоны и вывод](../Базовый%20синтаксис/Строковые%20шаблоны%20и%20вывод.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `s.equals(t)` | `s == t` |
> | `s == t` (ссылки) | `s === t` |
> | `s.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](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Строки.md)
> [!warning] Подводные камни
> **`length` считает кодовые единицы UTF-16, а не символы.** Эмодзи и редкие иероглифы занимают две:
> ```kotlin
> println("😀".length) // 2, а не 1
> println("😀".count()) // 2 — то же самое
> println("😀".codePointCount(0, "😀".length)) // 1 — настоящее число символов
> ```
> Разрезать такую строку по индексу - значит получить битые символы.
>
> **`split` с пустым разделителем и пустой строкой дают неожиданный результат:**
> ```kotlin
> println("".split(",")) // [] — один пустой элемент, размер 1
> println("".split(",").size) // 1
> println("a,,b".split(",")) // [a, , b] — пустой элемент сохраняется
> ```
>
> **`trimIndent` не обрезает пробелы внутри строк** - только общий отступ.
>
> **`replace` принимает строку, а не regex** - в отличие от Java:
> ```kotlin
> println("a.b".replace(".", "-")) // a-b — точка как символ
> println("a.b".replace(Regex("."), "-")) // --- — regex
> ```
> В Java `String.replaceAll` работает с regex, и это частый источник путаницы при переходе.
>
> **Строка в `when` сравнивается по значению, но полнота не проверяется** - для `String` всегда нужен `else`.
Дополнительно: [Char](Char.md)
Далее: [Char](Char.md)