Kotlin guide done. (20.10.26)
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user