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,140 @@
---
title: Char
description: Char как отдельный тип, код символа, проверки категорий и работа с цифрами
tags:
- kotlin
- stdlib
---
`Char` - один символ в одинарных кавычках. Хранит кодовую единицу UTF-16 (16 бит).
```kotlin
val c: Char = 'A'
val tab = '\t'
val quote = '\''
val unicode = '\u00A9' // ©
```
Ключевое отличие от Java: **`Char` не является числом**. Арифметику с ним делать можно, но неявного превращения в `Int` нет.
```kotlin
val c = 'A'
// val n: Int = c // ошибка компиляции
val n: Int = c.code // 65 — явно
println(c + 1) // B — Char + Int = Char
println('z' - 'a') // 25 — Char - Char = Int
println(c.toString()) // A
```
| Выражение | Результат | Тип |
|---|---|---|
| `'A' + 1` | `'B'` | `Char` |
| `'z' - 'a'` | `25` | `Int` |
| `'A'.code` | `65` | `Int` |
| `65.toChar()` | `'A'` | `Char` |
## Проверки категорий
```kotlin
println('a'.isLetter()) // true
println('5'.isDigit()) // true
println('a'.isLetterOrDigit()) // true
println(' '.isWhitespace()) // true
println('a'.isLowerCase()) // true
println('A'.isUpperCase()) // true
```
Все эти функции учитывают Unicode: `'я'.isLetter()` тоже `true`.
## Регистр
```kotlin
println('a'.uppercaseChar()) // A — возвращает Char
println('A'.lowercaseChar()) // a
println('a'.uppercase()) // "A" — возвращает String
println('ß'.uppercase()) // "SS" — два символа, поэтому String
```
Есть две версии: `uppercaseChar()` даёт `Char`, `uppercase()` - `String`. Вторая нужна, потому что в Unicode регистр не всегда меняется один-к-одному.
## Цифры
```kotlin
println('7'.digitToInt()) // 7 — число, а не код символа
println('f'.digitToInt(16)) // 15
println('x'.digitToIntOrNull()) // null — безопасная версия
println(7.digitToChar()) // '7'
```
Это главный способ разобрать цифру. Старый трюк из Java (`c - '0'`) тоже работает, но `digitToInt()` понятнее и проверяет корректность:
```kotlin
println('7' - '0') // 7 — работает
// println('x'.digitToInt()) // IllegalArgumentException
```
## Диапазоны и сравнение
```kotlin
println('a' in 'a'..'z') // true
println('5' in '0'..'9') // true
for (c in 'a'..'e') print(c) // abcde
println('a' < 'b') // true — по коду
println(('a'..'e').toList()) // [a, b, c, d, e]
```
## Строки и Char
```kotlin
val s = "Kotlin"
println(s[0]) // K — элемент строки это Char
println(s.first()) // K
println(s.toCharArray().size) // 6
println(String(charArrayOf('a','b'))) // ab — собрать строку из массива
println(s.toList()) // [K, o, t, l, i, n]
println("abc".map { it + 1 }) // [b, c, d]
println(charArrayOf('a','b').concatToString()) // ab
```
Приписать `Char` к строке можно сложением - слева обязательно строка:
```kotlin
println("ab" + 'c') // abc
// println('c' + "ab") // ошибка: Char.plus ожидает Int
println("${'c'}ab") // cab — через шаблон
```
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `char` - числовой тип | `Char` - отдельный тип |
> | `int n = c;` работает | нужен `c.code` |
> | `Character.isDigit(c)` | `c.isDigit()` |
> | `Character.toUpperCase(c)` | `c.uppercaseChar()` |
> | `Character.getNumericValue(c)` | `c.digitToInt()` |
> | `c - '0'` для цифры | `c.digitToInt()` |
> | `switch (c)` по числам | `when (c)` по символам |
>
> В Java `char` участвует в арифметике как число, из-за чего `System.out.println('a' + 1)` печатает `98`, а не `b`. В Kotlin тот же код даёт `b` - тип сохраняется.
>
> Все утилиты из `Character` стали методами `Char`, поэтому пишутся через точку и находятся автодополнением.
>
> Сравнить: [Character в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Character.md)
> [!warning] Подводные камни
> **`Char` не вмещает символы за пределами BMP.** Эмодзи и редкие иероглифы кодируются двумя `Char` (surrogate pair):
> ```kotlin
> val emoji = "😀"
> println(emoji.length) // 2
> println(emoji[0].isLetter()) // false — половина пары бессмысленна
> println(emoji.codePointAt(0)) // 128512 — полный код символа
> ```
> Для посимвольной обработки текста с эмодзи нужны кодовые точки, а не `Char`.
>
> **`digitToInt()` бросает исключение, а не возвращает -1:**
> ```kotlin
> println('x'.digitToIntOrNull()) // null — безопасно
> // println('x'.digitToInt()) // IllegalArgumentException
> ```
>
> **`'1'.code` - это 49, а не 1.** Путать код символа с его числовым значением - классическая ошибка:
> ```kotlin
> println('1'.code) // 49
> println('1'.digitToInt()) // 1
> ```
>
> **`Char` в `when` требует `else`** - перечислить все 65 536 значений компилятор не может.
>
> **`uppercase()` возвращает `String`, и это не придирка.** `'ß'.uppercase()` даёт `"SS"` - два символа. Если нужен именно `Char`, используйте `uppercaseChar()`, но помните, что для `'ß'` он вернёт сам `'ß'`.
Далее: [Числа и переполнение](Числа%20и%20переполнение.md)
@@ -0,0 +1,218 @@
---
title: Деструктуризация
description: Разбор объекта на переменные, componentN, деструктуризация в лямбдах и циклах
tags:
- kotlin
- syntax
---
Деструктуризация - разбор объекта на отдельные переменные одной конструкцией.
```kotlin
data class Point(val x: Int, val y: Int)
fun main() {
val (x, y) = Point(1, 2)
println("$x $y") // 1 2
}
```
Компилятор превращает это в вызовы `component1()`, `component2()` и так далее:
```kotlin
data class Point(val x: Int, val y: Int)
fun main() {
val p = Point(1, 2)
val x = p.component1()
val y = p.component2()
println("$x $y") // 1 2
}
```
## Что можно деструктурировать
Любой тип, у которого есть функции `componentN()`. Автоматически они появляются у:
- [data class](../Базовые%20типы%20ООП/Data%20class.md) - по свойствам основного конструктора
- `Pair` и `Triple`
- `Map.Entry` - ключ и значение
- списков и массивов (первые 5 элементов)
```kotlin
fun main() {
val (a, b) = Pair(1, "x")
val (c, d, e) = Triple(1, 2, 3)
val (first, second) = listOf("a", "b", "c") // третий игнорируется
println("$a $b | $c $d $e | $first $second")
}
```
## Свой componentN
Для обычного класса операторы объявляются вручную:
```kotlin
class Range2(val from: Int, val to: Int) {
operator fun component1() = from
operator fun component2() = to
}
fun main() {
val (from, to) = Range2(1, 10)
println("$from..$to") // 1..10
}
```
Слово `operator` обязательно.
## Пропуск компонентов
```kotlin
data class User(val id: Int, val name: String, val email: String)
fun main() {
val (_, name, _) = User(1, "Иван", "i@x.ru")
println(name) // Иван
}
```
Подчёркивание означает «этот компонент не нужен» - `componentN()` для него даже не вызовется.
Если нужны только первые компоненты, лишние просто не перечисляют:
```kotlin
data class User(val id: Int, val name: String, val email: String)
fun main() {
val (id, name) = User(1, "Иван", "i@x.ru") // email не берём
println("$id $name") // 1 Иван
}
```
## В циклах
```kotlin
fun main() {
val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) {
println("$key=$value") // a=1, затем b=2
}
val list = listOf("x", "y")
for ((index, item) in list.withIndex()) {
println("$index:$item") // 0:x, затем 1:y
}
}
```
Это самое частое применение деструктуризации.
## В лямбдах
```kotlin
fun main() {
val map = mapOf("a" to 1, "b" to 2)
map.forEach { (k, v) -> println("$k$v") } // a1 b2
println(map.map { (k, v) -> "$k=$v" }) // [a=1, b=2]
map.forEach { (_, v) -> print(v) } // 12
println()
val points = listOf(1 to 2, 3 to 4)
println(points.map { (x, y) -> x + y }) // [3, 7]
}
```
Параметр в скобках `{ (k, v) -> }` - это **один** параметр, который разбирается, а не два отдельных. Без скобок `{ k, v -> }` лямбда ожидала бы два аргумента, и код не скомпилировался бы.
## Возврат нескольких значений
Классическое применение - функция, которая должна вернуть пару значений:
```kotlin
data class Result2(val value: Int, val error: String?)
fun parse(s: String): Result2 =
s.toIntOrNull()?.let { Result2(it, null) } ?: Result2(0, "не число")
fun main() {
val (value, error) = parse("42")
println("$value $error") // 42 null
val (v2, e2) = parse("abc")
println("$v2 $e2") // 0 не число
}
```
`Pair` для этого тоже подходит, но свой `data class` лучше: имена компонентов осмысленны, а не `first`/`second`.
## Деструктуризация по именам (Kotlin 2.3+)
Классическая деструктуризация позиционная - порядок переменных должен совпадать с порядком свойств. Это хрупко: перестановка свойств в `data class` молча меняет смысл кода.
С Kotlin 2.3 разрабатывается деструктуризация по именам. Синтаксис меняется так:
- `val [a, b] = obj` - **квадратные** скобки, позиционно (как было)
- `val (name, email) = obj` - **круглые** скобки, теперь **по именам** свойств
```kotlin
data class User(val name: String, val email: String)
fun main() {
val user = User("ИМЯ", "ПОЧТА")
val (email, name) = user // по именам — порядок не важен
println("name=$name email=$email") // name=ИМЯ email=ПОЧТА
val [first, second] = user // позиционно
println("first=$first") // first=ИМЯ
}
```
Фича пока за флагом компилятора:
```bash
kotlinc -Xname-based-destructuring=complete App.kt
```
Значения флага: `only-syntax` (включить синтаксис `[]` и полную форму), `name-mismatch` (предупреждать, когда имена в позиционной деструктуризации не совпадают со свойствами), `complete` (включить короткую форму по именам).
> [!warning] Это ломающее изменение
> Круглые скобки меняют смысл: был порядок, станут имена. Код `val (email, name) = user` сегодня присвоит `email` первое свойство, а после включения фичи - свойство с именем `email`. Пока флаг не включён, вся деструктуризация в вашем коде позиционная.
> [!info] Отличия от Java
> В Java деструктуризации объектов нет. Ближайший аналог - record patterns из Java 21:
> ```java
> record Point(int x, int y) {}
> if (obj instanceof Point(int x, int y)) { ... }
> ```
> Но это работает только в `instanceof` и `switch`, а не при обычном объявлении переменных. Разбор `Map.Entry` в цикле в Java невозможен - нужно писать `e.getKey()` и `e.getValue()`.
>
> | Java | Kotlin |
> |---|---|
> | `var k = e.getKey(); var v = e.getValue();` | `val (k, v) = e` |
> | `for (var e : map.entrySet())` + геттеры | `for ((k, v) in map)` |
> | `p.x(); p.y();` | `val (x, y) = p` |
> | нет возврата нескольких значений | `Pair`, `Triple`, `data class` |
>
> Сравнить: [Record в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовые%20типы%20ООП/Record.md)
> [!warning] Подводные камни
> **Деструктуризация позиционная, а не по именам.** Это главная опасность:
> ```kotlin
> data class User(val name: String, val email: String)
>
> fun main() {
> val (name, email) = User("Иван", "i@x.ru")
> println("$name / $email") // Иван / i@x.ru
> }
> ```
> Если позже в `data class` поменять порядок свойств на `(email, name)`, этот код **продолжит компилироваться**, но переменные получат чужие значения. Компилятор не поможет, потому что типы совпадают. Поэтому порядок свойств в `data class` - часть публичного API.
>
> **У обычного класса деструктуризации нет** - только у `data class` или при ручном объявлении `componentN()`.
>
> **В лямбде нужны скобки:**
> ```kotlin
> fun main() {
> val map = mapOf("a" to 1)
> map.forEach { (k, v) -> println("$k$v") } // верно: один параметр-пара
> map.forEach { e -> println("${e.key}${e.value}") } // тоже верно
> }
> ```
> Запись `{ k, v -> }` для `forEach` на `Map` не скомпилируется.
>
> **Список короче числа переменных - исключение в рантайме:**
> ```kotlin
> fun main() {
> val list = listOf(1)
> val (a, b) = list // компилируется!
> println("$a $b") // IndexOutOfBoundsException
> }
> ```
> У `List` есть `component1()`..`component5()`, и компилятор не знает фактическую длину. С `data class` такой проблемы нет.
>
> **`component1()` вызывается даже для неиспользуемой переменной** - если это не `_`. Для свойства с тяжёлым геттером лишняя работа.
>
> **Деструктуризация `Pair` теряет смысл имён.** `val (a, b) = getCoordinates()` не говорит, что где. Лучше `data class Coordinates(val lat: Double, val lon: Double)`.
Далее: [Приведение типов](Приведение%20типов.md)
@@ -0,0 +1,273 @@
---
title: Лямбды и функциональные типы
description: Синтаксис лямбд, it, функциональные типы, ссылки на функции и анонимные функции
tags:
- kotlin
- functional
---
Функциональный тип описывает функцию как значение: `(параметры) -> результат`.
```kotlin
val square: (Int) -> Int = { x -> x * x }
val greet: (String) -> String = { "Привет, $it" }
val action: () -> Unit = { println("сделано") }
val sum: (Int, Int) -> Int = { a, b -> a + b }
fun main() {
println(square(4)) // 16
println(greet("мир")) // Привет, мир
action() // сделано
println(sum(2, 3)) // 5
}
```
## Синтаксис лямбды
Лямбда всегда в фигурных скобках:
```kotlin
{ параметры -> тело }
```
```kotlin
val f = { x: Int, y: Int -> x + y } // типы указаны в лямбде
val g: (Int) -> Int = { it * 2 } // тип указан у переменной
```
Тип нужно указать либо у переменной, либо у параметров лямбды - где-то компилятор должен его увидеть.
Результат - **последнее выражение** тела, `return` не нужен и в обычной лямбде запрещён:
```kotlin
val describe: (Int) -> String = { n ->
val prefix = if (n > 0) "плюс" else "минус"
"$prefix $n" // это и есть результат
}
fun main() { println(describe(5)) } // плюс 5
```
## it - неявный параметр
Если параметр один, его можно не объявлять:
```kotlin
val double: (Int) -> Int = { it * 2 }
println(listOf(1, 2, 3).map { it * 10 }) // [10, 20, 30]
```
`it` удобен в короткой лямбде. Если тело длинное или лямбды вложены - давайте имя:
```kotlin
val users = listOf("Иван", "Пётр")
println(users.map { name -> name.uppercase() }) // [ИВАН, ПЁТР]
```
## Лямбда последним аргументом
Если последний параметр функции - функция, лямбду выносят за скобки:
```kotlin
fun measure(name: String, block: () -> Unit) {
val start = System.nanoTime()
block()
println("$name занял ${System.nanoTime() - start} нс")
}
fun main() {
measure("цикл") { // лямбда вне скобок
repeat(1000) { }
}
measure("пусто", { }) // так тоже можно, но не принято
}
```
Когда функция принимает **только** лямбду, круглые скобки опускаются совсем. Именно на этом правиле построены `forEach`, `let`, `apply` и все DSL:
```kotlin
listOf(1, 2).forEach { println(it) }
```
Подробнее: [DSL](../../Функциональный%20Kotlin/DSL.md)
## Неиспользуемые параметры
```kotlin
val map = mapOf("a" to 1)
map.forEach { (_, value) -> println(value) } // 1 — ключ не нужен
```
Подчёркивание говорит компилятору, что параметр намеренно игнорируется.
## Ссылки на функции - ::
Вместо лямбды, которая только вызывает существующую функцию, используют ссылку:
```kotlin
fun isEven(n: Int) = n % 2 == 0
fun main() {
val nums = listOf(1, 2, 3, 4)
println(nums.filter { isEven(it) }) // [2, 4] — лямбда-обёртка
println(nums.filter(::isEven)) // [2, 4] — ссылка, короче
nums.forEach(::println)
}
```
Виды ссылок:
```kotlin
class User(val name: String) {
fun greet() = "Привет, $name"
}
fun main() {
val byName: (User) -> String = User::name // ссылка на свойство
val greeter: (User) -> String = User::greet // на метод класса
val factory: (String) -> User = ::User // на конструктор
val u = User("Иван")
println(byName(u)) // Иван
println(greeter(u)) // Привет, Иван
println(factory("Пётр").name) // Пётр
val bound = u::greet // привязанная к объекту
println(bound()) // Привет, Иван
}
```
## Анонимные функции
Когда нужен явный `return` или явный тип результата:
```kotlin
val parse = fun(s: String): Int? {
if (s.isBlank()) return null // return работает
return s.toIntOrNull()
}
fun main() {
println(parse(" ")) // null
println(parse("42")) // 42
}
```
Отличие от лямбды: `return` выходит из самой анонимной функции, а в лямбде он запрещён (или выходит из внешней функции, если лямбда inline).
## Функциональные типы в подробностях
```kotlin
val a: () -> Unit // без параметров, без результата
val b: (Int) -> String // один параметр
val c: (Int, Int) -> Int // два параметра
val d: ((Int) -> Int) -> Int // принимает функцию
val e: (Int) -> ((Int) -> Int) // возвращает функцию
val f: String.(Int) -> String // с получателем (receiver)
val g: ((Int) -> Int)? // nullable
val h: suspend () -> Unit // suspend-функция
```
Тип с получателем вызывается как метод:
```kotlin
val repeatTimes: String.(Int) -> String = { n -> this.repeat(n) }
fun main() {
println("ab".repeatTimes(2)) // abab
}
```
На этом построены `apply`, `buildString` и DSL. Подробнее: [Receivers и context parameters](../../Функциональный%20Kotlin/Receivers%20и%20context%20parameters.md)
## Замыкания
Лямбда захватывает переменные из окружения - **включая изменяемые**:
```kotlin
fun counter(): () -> Int {
var count = 0 // захвачена по ссылке
return { ++count }
}
fun main() {
val next = counter()
println(next()) // 1
println(next()) // 2
println(next()) // 3
}
```
Это важное отличие от Java, где захватить можно только effectively final переменную.
```kotlin
fun main() {
var sum = 0
listOf(1, 2, 3).forEach { sum += it } // в Java так нельзя
println(sum) // 6
}
```
## Вызов
```kotlin
val f: (Int) -> Int = { it + 1 }
fun main() {
println(f(1)) // 2 — обычный вызов
println(f.invoke(1)) // 2 — то же самое
val g: ((Int) -> Int)? = null
println(g?.invoke(1)) // null — безопасный вызов nullable-функции
}
```
## Что происходит под капотом
Функциональный тип компилируется в интерфейс `FunctionN`:
```kotlin
val f: (Int) -> Int = { it * 2 } // объект класса Function1<Int, Int>
```
Это значит аллокацию на каждую лямбду, если она захватывает переменные. Для функций высшего порядка в горячем коде существует `inline` - он подставляет тело лямбды в место вызова, и объекта не создаётся вовсе.
Подробнее: [Inline-функции](../../Функциональный%20Kotlin/Inline-функции.md), [Kotlin в байт-коде](../../Взаимодействие%20с%20Java/Kotlin%20в%20байт-коде.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `x -> x * 2` | `{ x -> x * 2 }` или `{ it * 2 }` |
> | `Function<Integer,Integer>` | `(Int) -> Int` |
> | `BiFunction<A,B,C>` | `(A, B) -> C` |
> | `Supplier<T>` | `() -> T` |
> | `Consumer<T>` | `(T) -> Unit` |
> | `Predicate<T>` | `(T) -> Boolean` |
> | `Runnable` | `() -> Unit` |
> | `String::length` | `String::length` |
> | нет неявного параметра | `it` |
> | захват только effectively final | захват изменяемых переменных |
> | лямбда только для SAM-интерфейса | функциональный тип первого класса |
>
> В Java лямбда - это реализация функционального интерфейса, поэтому для каждой формы нужен свой тип (`Function`, `BiFunction`, `Supplier`, `Consumer`, `Predicate`…). В Kotlin тип функции записывается напрямую: `(Int, String) -> Boolean` не требует придумывать имя.
>
> Захват изменяемых переменных - практическая разница. Java требует `final` или effectively final, вынуждая использовать массив-обёртку `int[] sum = {0}`. В Kotlin `var sum = 0` захватывается как есть (компилятор оборачивает его в `Ref`-объект).
>
> Сравнить: [Функции в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Функции.md)
> [!warning] Подводные камни
> **`return` в лямбде запрещён** - он относился бы к внешней функции:
> ```kotlin
> fun find(items: List<Int>): Int? {
> items.forEach {
> if (it > 1) return it // выход из find, а не из лямбды!
> } // работает только потому, что forEach inline
> return null
> }
>
> fun main() { println(find(listOf(1, 2, 3))) } // 2
> ```
> Это «нелокальный return», он разрешён только в inline-функциях. Для выхода из самой лямбды нужна метка:
> ```kotlin
> fun main() {
> listOf(1, 2, 3).forEach {
> if (it == 2) return@forEach // пропустить итерацию, как continue
> println(it) // 1, затем 3
> }
> }
> ```
>
> **`it` во вложенных лямбдах затеняется:**
> ```kotlin
> fun main() {
> val nested = listOf(listOf(1, 2))
> // println(nested.map { it.map { it * 2 } }) // непонятно, какое it
> println(nested.map { row -> row.map { v -> v * 2 } }) // [[2, 4]]
> }
> ```
>
> **Каждая не-inline лямбда с захватом - объект.** В цикле на миллион итераций это миллион аллокаций. Стандартные операции (`map`, `filter`, `forEach`) объявлены `inline`, поэтому бесплатны; свои функции высшего порядка в горячем коде тоже стоит делать `inline`.
>
> **Лямбда, захватившая `this`, держит объект в памяти** - классическая утечка в Android, когда колбэк переживает активити.
>
> **Ссылка на перегруженную функцию неоднозначна:**
> ```kotlin
> fun show(x: Int) = "int"
> fun show(x: String) = "str"
>
> fun main() {
> // val f = ::show // ошибка: какая из двух?
> val f: (Int) -> String = ::show // тип разрешает неоднозначность
> println(f(1)) // int
> }
> ```
Дополнительно: [Функции высшего порядка](../../Функциональный%20Kotlin/Функции%20высшего%20порядка.md)
Далее: [Деструктуризация](Деструктуризация.md)
@@ -0,0 +1,249 @@
---
title: Массивы
description: Array, специализированные IntArray, сравнение через contentEquals и почему обычно нужен List
tags:
- kotlin
- stdlib
---
Массив - фиксированная по размеру последовательность элементов. В Kotlin это класс `Array<T>`, а не синтаксическая конструкция.
```kotlin
val a = arrayOf(1, 2, 3)
val b = Array(3) { it * 2 } // [0, 2, 4] — по лямбде от индекса
val c = arrayOfNulls<String>(3) // [null, null, null]
val d = emptyArray<Int>()
println(a[0]) // 1
a[0] = 10
println(a.size) // 3
println(a.joinToString()) // 10, 2, 3
```
Размер задаётся при создании и не меняется. Элементы изменяемы всегда - даже если массив объявлен через `val`.
## Специализированные массивы
Для чисел есть отдельные классы без упаковки:
```kotlin
val ints = intArrayOf(1, 2, 3)
val zeros = IntArray(5) // [0, 0, 0, 0, 0] — заполнен нулями
val squares = IntArray(5) { it * it } // [0, 1, 4, 9, 16]
val doubles = doubleArrayOf(1.0, 2.0)
val chars = charArrayOf('a', 'b')
val bools = BooleanArray(3) // [false, false, false]
```
| Класс | Байт-код | Аналог в Java |
|---|---|---|
| `IntArray` | `int[]` | `int[]` |
| `LongArray` | `long[]` | `long[]` |
| `DoubleArray` | `double[]` | `double[]` |
| `CharArray` | `char[]` | `char[]` |
| `BooleanArray` | `boolean[]` | `boolean[]` |
| `Array<Int>` | `Integer[]` | `Integer[]` |
Разница принципиальна: `Array<Int>` хранит **упакованные** объекты, `IntArray` - примитивы. В горячем коде это заметно по памяти и скорости.
```kotlin
val boxed: Array<Int> = arrayOf(1, 2, 3) // Integer[] — аллокация на элемент
val raw: IntArray = intArrayOf(1, 2, 3) // int[] — без упаковки
println("${boxed.size} ${raw.size}")
```
## Сравнение - только contentEquals
`==` на массивах сравнивает **ссылки**, а не содержимое. Это главная ловушка:
```kotlin
val a = arrayOf(1, 2, 3)
val b = arrayOf(1, 2, 3)
println(a == b) // false — разные объекты!
println(a.contentEquals(b)) // true — так правильно
println(a.contentHashCode()) // хеш по содержимому
```
Для вложенных массивов - глубокие версии:
```kotlin
val x = arrayOf(arrayOf(1), arrayOf(2))
val y = arrayOf(arrayOf(1), arrayOf(2))
println(x.contentEquals(y)) // false — сравнивает ссылки вложенных
println(x.contentDeepEquals(y)) // true
```
## Печать
`toString()` массива бесполезен:
```kotlin
val a = arrayOf(1, 2, 3)
println(a) // [Ljava.lang.Integer;@1b6d3586
println(a.contentToString()) // [1, 2, 3]
println(a.joinToString()) // 1, 2, 3
println("$a".startsWith("[L")) // true — шаблон вызывает тот же toString
```
Внутри строкового шаблона всегда нужен `contentToString()` или `joinToString()`.
## Обход
```kotlin
val a = arrayOf("x", "y", "z")
for (item in a) print(item) // xyz
for (i in a.indices) print(a[i]) // xyz
for ((i, v) in a.withIndex()) print("$i$v ") // 0x 1y 2z
a.forEach { print(it) } // xyz
a.forEachIndexed { i, v -> print("$i$v ") }
println()
println(a.lastIndex) // 2
```
## Операции
Массивы поддерживают те же операции, что коллекции:
```kotlin
val a = intArrayOf(3, 1, 2)
println(a.sorted()) // [1, 2, 3] — возвращает List!
println(a.sortedArray()) // новый IntArray
a.sort() // сортировка на месте
println(a.contentToString()) // [1, 2, 3]
println(a.sum()) // 6
println(a.average()) // 2.0
println(a.max()) // 3
println(a.filter { it > 1 }) // [2, 3] — List
println(a.map { it * 2 }) // [2, 4, 6] — List
println(a.reversed()) // [3, 2, 1] — List
println(a.reversedArray().contentToString()) // [3, 2, 1]
println(a.contains(2)) // true
println(2 in a) // true
println(a.indexOf(2)) // 1
```
Обратите внимание: `filter`, `map`, `sorted`, `reversed` возвращают `List`, а не массив. Версии, сохраняющие тип, имеют суффикс `Array`.
## Копирование и изменение размера
Размер массива неизменен, поэтому «добавление» - это создание нового:
```kotlin
val a = intArrayOf(1, 2, 3)
println((a + 4).contentToString()) // [1, 2, 3, 4] — новый массив
println(a.copyOf().contentToString()) // [1, 2, 3]
println(a.copyOf(5).contentToString()) // [1, 2, 3, 0, 0] — дополнен нулями
println(a.copyOf(2).contentToString()) // [1, 2] — обрезан
println(a.copyOfRange(1, 3).contentToString()) // [2, 3]
println(a.sliceArray(0..1).contentToString()) // [1, 2]
a.fill(0)
println(a.contentToString()) // [0, 0, 0]
```
## Преобразование в коллекции и обратно
```kotlin
val a = arrayOf(1, 2, 3)
val list: List<Int> = a.toList()
val set = a.toSet()
val back: Array<Int> = list.toTypedArray()
val raw: IntArray = list.toIntArray()
println("${list} ${set} ${back.size} ${raw.size}")
```
## Многомерные массивы
Отдельного типа нет - это массив массивов:
```kotlin
val grid = Array(3) { IntArray(4) } // 3x4, заполнен нулями
grid[1][2] = 5
println(grid[1].contentToString()) // [0, 0, 5, 0]
val table = Array(2) { r -> IntArray(2) { c -> r * 2 + c } }
println(table.joinToString { it.contentToString() }) // [0, 1], [2, 3]
```
Строки могут иметь разную длину (рваный массив):
```kotlin
val jagged = arrayOf(intArrayOf(1), intArrayOf(1, 2, 3))
println(jagged.joinToString { it.size.toString() }) // 1, 3
```
## Когда массив, а когда List
Массив нужен редко. Используйте его, если:
- важна производительность и нужны примитивы без упаковки (`IntArray`)
- работаете с API, требующим массив (`vararg`, Java-библиотеки)
- размер фиксирован и известен заранее
В остальных случаях - `List`: он не даёт менять себя случайно, имеет больше операций и корректный `equals`.
```kotlin
val list = listOf(1, 2, 3)
println(list == listOf(1, 2, 3)) // true — работает из коробки
```
Подробнее: [Коллекции](../../Коллекции/Коллекции.md)
## vararg
Параметр с переменным числом аргументов внутри функции - массив:
```kotlin
fun sum(vararg nums: Int): Int {
var total = 0
for (n in nums) total += n // nums это IntArray
return total
}
fun main() {
println(sum(1, 2, 3)) // 6
println(sum()) // 0
val arr = intArrayOf(1, 2)
println(sum(*arr)) // 3 — spread-оператор
}
```
Подробнее: [Функции](Функции.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `int[] a = {1,2,3}` | `val a = intArrayOf(1,2,3)` |
> | `new int[5]` | `IntArray(5)` |
> | `a.length` | `a.size` |
> | `Arrays.toString(a)` | `a.contentToString()` |
> | `Arrays.equals(a,b)` | `a.contentEquals(b)` |
> | `Arrays.deepEquals(a,b)` | `a.contentDeepEquals(b)` |
> | `Arrays.sort(a)` | `a.sort()` |
> | `Arrays.copyOf(a,n)` | `a.copyOf(n)` |
> | `Arrays.fill(a,0)` | `a.fill(0)` |
> | `int[]` и `Integer[]` | `IntArray` и `Array<Int>` |
> | `f(arr)` для varargs | `f(*arr)` — нужен spread |
>
> Статические методы `Arrays` стали методами массива - находятся автодополнением, а не поиском по документации.
>
> Разделение `IntArray` / `Array<Int>` сделано намеренно: в Java `Integer[]` и `int[]` тоже разные, но это легко не заметить. В Kotlin разные имена не позволяют спутать упакованный массив с примитивным.
>
> **Массивы в Kotlin инвариантны**, в Java - ковариантны. Java разрешает `Object[] o = new String[1]; o[0] = 1;` и падает в рантайме (`ArrayStoreException`). В Kotlin `Array<Any> = Array<String>` не скомпилируется. Подробнее: [Вариантность](../../Дженерики/Вариантность.md)
>
> Сравнить: [Массивы в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Массивы.md)
> [!warning] Подводные камни
> **`==` на массивах сравнивает ссылки.** Самая частая ошибка:
> ```kotlin
> val a = arrayOf(1, 2)
> println(a == arrayOf(1, 2)) // false
> println(a.contentEquals(arrayOf(1, 2))) // true
> ```
> По этой же причине массив нельзя использовать как ключ `Map` или в `Set` - `hashCode` считается по ссылке.
>
> **Массив внутри `data class` ломает `equals`:**
> ```kotlin
> data class Packet(val data: ByteArray)
>
> fun main() {
> val p1 = Packet(byteArrayOf(1))
> println(p1 == Packet(byteArrayOf(1))) // false!
> }
> ```
> Сгенерированный `equals` сравнивает массив по ссылке, а не по содержимому. Поэтому в `data class` с массивом `equals`/`hashCode` пишут руками. Подробнее: [Data class](../Базовые%20типы%20ООП/Data%20class.md)
>
> **`println(array)` печатает мусор** вида `[Ljava.lang.Integer;@1b6d3586`. То же в строковых шаблонах.
>
> **`val` не защищает элементы:**
> ```kotlin
> val a = intArrayOf(1, 2)
> a[0] = 99 // разрешено
> println(a.contentToString()) // [99, 2]
> ```
> Неизменяемость даёт `List`, а не `val`.
>
> **`Array<Int>` вместо `IntArray` - скрытая просадка.** Каждый элемент упакован в `Integer`: больше памяти и промахи кеша.
>
> **`arrayOfNulls` и `Array(n)` для непримитивных типов требуют разного подхода:**
> ```kotlin
> val a = arrayOfNulls<String>(3) // Array<String?>
> val b = Array(3) { "" } // Array<String> — инициализатор обязателен
> println("${a.size} ${b.size}")
> ```
> Создать `Array<String>` без инициализатора нельзя - компилятор не позволит оставить non-null элементы пустыми.
Дополнительно: [Коллекции](../../Коллекции/Коллекции.md)
Далее: [Функции](Функции.md)
@@ -0,0 +1,254 @@
---
title: Приведение типов
description: Проверка is, приведение as и as?, числовые преобразования и стирание типов
tags:
- kotlin
- types
---
## Проверка типа - is
```kotlin
fun describe(x: Any): String = when {
x is String -> "строка длины ${x.length}"
x is Int -> "число ${x + 1}"
x !is Boolean -> "не логическое"
else -> "логическое"
}
fun main() {
println(describe("ab")) // строка длины 2
println(describe(5)) // число 6
println(describe(1.0)) // не логическое
}
```
После успешной проверки компилятор сам приводит тип - это [умное приведение](../Null-безопасность/Умные%20приведения.md), явный каст не нужен.
## Небезопасное приведение - as
```kotlin
fun main() {
val x: Any = "text"
val s = x as String // ok
println(s.length) // 4
val n: Any = 42
// val bad = n as String // ClassCastException
}
```
`as` используют, когда тип известен точно, а компилятор об этом знать не может.
## Безопасное приведение - as?
Возвращает `null` вместо исключения:
```kotlin
fun main() {
val x: Any = "text"
println(x as? Int) // null
println(x as? String) // text
println((x as? Int) ?: 0) // 0 — частая связка с elvis
}
```
Результат всегда nullable, даже при приведении к non-null типу.
Сравнение способов:
| Запись | При несовпадении типа |
|---|---|
| `x is T` | `false` |
| `x as T` | `ClassCastException` |
| `x as? T` | `null` |
## Приведение к nullable
```kotlin
fun main() {
val a: Any? = null
println(a as? String) // null
// println(a as String) // NPE: null cannot be cast to non-null type
println(a as String?) // null — приведение к nullable разрешено
}
```
## Числовые преобразования
Для чисел `as` не применяется - только явные функции:
```kotlin
fun main() {
val i = 42
println(i.toLong()) // 42
println(i.toDouble()) // 42.0
println(i.toByte()) // 42
println(i.toChar()) // *
println(3.99.toInt()) // 3 — обрезание, не округление
println("42".toInt()) // 42
}
```
Неявных преобразований между числовыми типами нет:
```kotlin
fun main() {
val i: Int = 42
// val l: Long = i // ошибка компиляции
val l: Long = i.toLong() // так
}
```
Потеря данных происходит молча:
```kotlin
fun main() {
println(300.toByte()) // 44 — старшие биты отброшены
println(1e20.toInt()) // 2147483647 — обрезано
println(Double.NaN.toInt()) // 0
}
```
Подробнее: [Числа и переполнение](Числа%20и%20переполнение.md)
## Стирание типов и дженерики
Параметры типа стираются при компиляции, поэтому проверить их в рантайме нельзя:
```kotlin
fun check(x: Any) {
if (x is List<*>) { // можно: звёздочка вместо типа
println("список из ${x.size}")
}
// if (x is List<String>) // ошибка: cannot check for instance
// // of erased type
}
fun main() { check(listOf(1, 2)) } // список из 2
```
`List<*>` - это star-проекция: «список чего-то неизвестного». Элементы из него читаются как `Any?`.
Приведение к конкретному дженерику компилируется с предупреждением и небезопасно:
```kotlin
@Suppress("UNCHECKED_CAST")
fun <T> castList(x: Any): List<T> = x as List<T> // unchecked cast
fun main() {
val nums: List<Int> = castList(listOf("a", "b")) // не упадёт здесь!
// println(nums.first() + 1) // ClassCastException только при обращении
println(nums.size) // 2 — работает, типы не проверяются
}
```
Это отложенное падение: ошибка проявится далеко от места приведения.
Обойти стирание можно через `reified` в inline-функциях:
```kotlin
inline fun <reified T> Any.safeCast(): T? = this as? T
fun main() {
println("text".safeCast<String>()) // text
println("text".safeCast<Int>()) // null
}
```
Подробнее: [Reified и inline](../../Дженерики/Reified%20и%20inline.md)
## Приведение в иерархии
```kotlin
open class Animal(val name: String)
class Dog(name: String) : Animal(name) {
fun bark() = "Гав"
}
fun main() {
val a: Animal = Dog("Рекс") // вверх по иерархии — неявно
println(a.name) // Рекс
val d = a as Dog // вниз — только явно
println(d.bark()) // Гав
val other: Animal = Animal("кто-то")
println((other as? Dog)?.bark()) // null — безопасно
}
```
Приведение вверх (к супертипу) всегда безопасно и неявно. Приведение вниз требует `as` или `as?`.
## Коллекции и вариантность
```kotlin
fun main() {
val strings: List<String> = listOf("a")
val anys: List<Any> = strings // ok — List ковариантен по out T
println(anys.size) // 1
val mStrings: MutableList<String> = mutableListOf("a")
// val mAnys: MutableList<Any> = mStrings // ошибка — MutableList инвариантен
}
```
`List<out T>` разрешает такое присваивание, `MutableList<T>` - нет, иначе можно было бы положить `Int` в список строк. Подробнее: [Вариантность](../../Дженерики/Вариантность.md)
## Получение типа в рантайме
```kotlin
fun main() {
val x: Any = "text"
println(x::class) // class kotlin.String
println(x::class.simpleName) // String
println(x::class.java) // class java.lang.String — Java-класс
println(x is CharSequence) // true — проверка по интерфейсу
}
```
Подробнее: [Рефлексия](../../Рефлексия/Рефлексия.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `x instanceof String` | `x is String` |
> | `(String) x` | `x as String` |
> | нет безопасного каста | `x as? String` |
> | `(int) doubleValue` | `doubleValue.toInt()` |
> | `long l = intValue;` неявно | `intValue.toLong()` явно |
> | `x.getClass()` | `x::class` или `x::class.java` |
> | после `instanceof` нужен каст | умное приведение |
> | массивы ковариантны | массивы инвариантны |
>
> Главные приобретения: `as?` вместо пары `instanceof` + каст, и запрет неявных числовых преобразований, из-за которых в Java `long total = intA * intB` тихо переполняется.
>
> Ковариантность массивов в Java - известная дыра в типизации:
> ```java
> Object[] objs = new String[1];
> objs[0] = 42; // компилируется, падает в рантайме
> ```
> В Kotlin `Array<T>` инвариантен, и такой код не соберётся.
>
> Сравнить: [Приведение ссылочных типов в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Приведение%20ссылочных%20типов.md)
> [!warning] Подводные камни
> **`as` на null даёт NPE, а не ClassCastException:**
> ```kotlin
> fun main() {
> val a: Any? = null
> // println(a as String) // NullPointerException
> println(a as String?) // null — ok
> }
> ```
>
> **`as?` маскирует обе причины.** `null` в результате означает либо неверный тип, либо что значение изначально было `null` - различить невозможно:
> ```kotlin
> fun main() {
> val a: Any? = null
> val b: Any = 42
> println("${a as? String} ${b as? String}") // null null
> }
> ```
>
> **Unchecked cast падает не там, где написан:**
> ```kotlin
> @Suppress("UNCHECKED_CAST")
> fun main() {
> val list = listOf("a") as List<Int> // здесь не падает
> println(list.size) // 1 — и здесь тоже
> // println(list[0] + 1) // ClassCastException только тут
> }
> ```
> Поэтому `@Suppress("UNCHECKED_CAST")` - всегда повод задуматься.
>
> **`toInt()` на `Double` обрезает дробную часть, а не округляет.** `3.99.toInt()` даёт `3`. Для округления - `roundToInt()`.
>
> **`toByte()` и `toShort()` молча теряют старшие биты:** `300.toByte()` возвращает `44`.
>
> **`is` не проверяет параметры дженерика.** `x is List<String>` не скомпилируется, а `x is List<*>` ничего не говорит об элементах. Проверять придётся вручную:
> ```kotlin
> fun main() {
> val x: Any = listOf(1, "a")
> val strings = (x as? List<*>)?.filterIsInstance<String>()
> println(strings) // [a] — только элементы нужного типа
> }
> ```
>
> **`x::class` и `x::class.java` - разные вещи.** Первое - `KClass` Kotlin, второе - `Class` Java. Для `Int` они могут отличаться (`int` против `Integer`).
Дополнительно: [Умные приведения](../Null-безопасность/Умные%20приведения.md)
Далее: [Any, Unit, Nothing](../Базовые%20типы%20ООП/Any,%20Unit,%20Nothing.md)
@@ -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)
@@ -0,0 +1,280 @@
---
title: Функции
description: Объявление функций, значения по умолчанию, именованные аргументы, vararg и локальные функции
tags:
- kotlin
- syntax
---
Функция объявляется словом `fun`. В Kotlin она может жить на верхнем уровне файла, внутри класса или внутри другой функции.
```kotlin
fun greet(name: String): String {
return "Привет, $name"
}
fun main() {
println(greet("Kotlin")) // Привет, Kotlin
}
```
Порядок: `fun` → имя → параметры → `:` тип результата → тело.
## Выражение вместо тела
Если функция - одно выражение, фигурные скобки и `return` убираются:
```kotlin
fun square(x: Int): Int = x * x
fun greet(name: String) = "Привет, $name" // тип выведен: String
fun max(a: Int, b: Int) = if (a > b) a else b
```
Это основная форма в идиоматичном Kotlin. Тип результата можно опустить - компилятор выведет его. Но для публичного API его лучше писать явно: это контракт, и его случайное изменение не должно быть незаметным.
## Unit - отсутствие результата
```kotlin
fun log(message: String): Unit {
println(message)
}
fun log2(message: String) { // : Unit можно опустить
println(message)
}
```
`Unit` - настоящий тип с единственным значением, а не пустота. Подробнее: [Any, Unit, Nothing](../Базовые%20типы%20ООП/Any,%20Unit,%20Nothing.md)
## Параметры
Параметры всегда **только для чтения** - это `val`, присвоить им нельзя:
```kotlin
fun bad(x: Int) {
// x = 5 // ошибка компиляции
val y = x + 1
println(y)
}
```
Тип параметра обязателен - вывода типов здесь нет.
## Значения по умолчанию
```kotlin
fun connect(host: String, port: Int = 8080, secure: Boolean = false) {
println("$host:$port secure=$secure")
}
fun main() {
connect("localhost") // localhost:8080 secure=false
connect("localhost", 443) // localhost:443 secure=false
connect("localhost", 443, true) // localhost:443 secure=true
}
```
Это заменяет перегрузки: вместо трёх версий метода - одна функция. Значением по умолчанию может быть любое выражение, включая вызов функции и другие параметры:
```kotlin
fun repeat2(text: String, times: Int = text.length) = text.repeat(times)
fun main() {
println(repeat2("ab")) // abab — times = 2
println(repeat2("ab", 3)) // ababab
}
```
Выражение вычисляется при каждом вызове, в котором аргумент опущен.
## Именованные аргументы
Аргумент можно передать по имени параметра:
```kotlin
fun connect(host: String, port: Int = 80, secure: Boolean = false, timeout: Int = 30) {
println("$host:$port s=$secure t=$timeout")
}
fun main() {
connect("localhost", secure = true) // пропустили port
connect(host = "localhost", timeout = 60)
connect(port = 443, host = "localhost") // порядок не важен
}
```
Две причины использовать:
1. **Пропустить** часть параметров со значениями по умолчанию
2. **Читаемость** на вызовах с непонятными литералами
```kotlin
// что здесь true?
// createUser("Иван", true, false, true)
// так понятно:
// createUser("Иван", admin = true, banned = false, verified = true)
```
Правило: позиционные аргументы идут до именованных. После первого именованного все следующие тоже должны быть именованными - кроме `vararg` со spread.
## vararg
Переменное число аргументов:
```kotlin
fun sum(vararg nums: Int): Int {
var total = 0
for (n in nums) total += n // nums имеет тип IntArray
return total
}
fun main() {
println(sum(1, 2, 3)) // 6
println(sum()) // 0
val arr = intArrayOf(1, 2, 3)
println(sum(*arr)) // 6 — spread-оператор обязателен
}
```
Правила:
- `vararg` может быть **только один** в функции
- он не обязан быть последним, но тогда остальные передаются по имени
- внутри функции это массив (`Array<T>` или специализированный)
- для передачи готового массива нужен `*` (spread)
```kotlin
fun tag(vararg classes: String, id: String) = "$id: ${classes.joinToString()}"
fun main() {
println(tag("a", "b", id = "x")) // x: a, b
}
```
## Локальные функции
Функция внутри функции - видит переменные внешней:
```kotlin
fun process(items: List<Int>): List<String> {
val prefix = ">> " // захватывается
fun format(x: Int): String = "$prefix$x" // локальная функция
return items.map { format(it) }
}
fun main() {
println(process(listOf(1, 2))) // [>> 1, >> 2]
}
```
Нужны, когда логика используется только внутри одной функции и не стоит того, чтобы выносить её в класс или в приватную функцию файла.
## Перегрузка
Функции с одним именем и разными параметрами:
```kotlin
fun show(x: Int) = "число $x"
fun show(x: String) = "строка $x"
fun show(x: Int, y: Int) = "пара $x,$y"
fun main() {
println(show(1)) // число 1
println(show("a")) // строка a
println(show(1, 2)) // пара 1,2
}
```
Сигнатуру образуют имя и типы параметров. Тип результата в неё **не входит** - две функции, отличающиеся только им, не скомпилируются.
Чаще всего перегрузка не нужна: её заменяют значения по умолчанию.
## Передача аргументов
Аргументы передаются **по значению**, как в Java:
- примитив - копируется значение
- объект - копируется ссылка
Значит, переприсвоить параметр внутри функции нельзя (он `val`), но изменяемый объект изменить можно:
```kotlin
fun addItem(list: MutableList<Int>) {
list.add(99) // объект изменён — виден снаружи
}
fun main() {
val items = mutableListOf(1)
addItem(items)
println(items) // [1, 99]
}
```
## Однострочные функции-расширения
Функции можно добавлять к существующим типам:
```kotlin
fun String.shout() = uppercase() + "!"
fun main() {
println("привет".shout()) // ПРИВЕТ!
}
```
Подробнее: [Функции-расширения](../../Функциональный%20Kotlin/Функции-расширения.md)
## Функции как значения
Функция может быть параметром и результатом:
```kotlin
fun apply2(x: Int, f: (Int) -> Int) = f(x)
fun main() {
println(apply2(3) { it * 2 }) // 6
println(apply2(3, ::square)) // 9 — ссылка на функцию
}
fun square(x: Int) = x * x
```
Подробнее: [Лямбды и функциональные типы](Лямбды%20и%20функциональные%20типы.md), [Функции высшего порядка](../../Функциональный%20Kotlin/Функции%20высшего%20порядка.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | метод только внутри класса | функция на верхнем уровне |
> | `int f(int x) { return x; }` | `fun f(x: Int) = x` |
> | `void` | `Unit` (можно опустить) |
> | нет значений по умолчанию | `fun f(x: Int = 0)` |
> | нет именованных аргументов | `f(x = 1)` |
> | `int... nums` | `vararg nums: Int` |
> | `f(arr)` для varargs | `f(*arr)` |
> | параметр можно переприсвоить | параметр - `val` |
> | перегрузки для опциональных параметров | одна функция со значениями по умолчанию |
>
> Значения по умолчанию + именованные аргументы убирают главный источник шума в Java API - «телескопические» перегрузки:
> ```java
> void connect(String host) { connect(host, 8080); }
> void connect(String host, int port) { connect(host, port, false); }
> void connect(String host, int port, boolean secure) { ... }
> ```
> В Kotlin это одна строка. Для вызова из Java есть `@JvmOverloads`, который генерирует такие перегрузки автоматически - см. [Kotlin из Java](../../Взаимодействие%20с%20Java/Kotlin%20из%20Java.md).
>
> Сравнить: [Функции в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Функции.md)
> [!warning] Подводные камни
> **Изменяемое значение по умолчанию вычисляется на каждом вызове** - в отличие от Python, где оно создаётся один раз. Это как раз правильное поведение:
> ```kotlin
> fun add(item: Int, list: MutableList<Int> = mutableListOf()): List<Int> {
> list.add(item)
> return list
> }
>
> fun main() {
> println(add(1)) // [1]
> println(add(2)) // [2] — новый список, а не [1, 2]
> }
> ```
>
> **Значения по умолчанию не видны из Java.** Без `@JvmOverloads` Java-код обязан передавать все аргументы.
>
> **Переопределённый метод не может изменить значения по умолчанию:**
> ```kotlin
> open class A { open fun f(x: Int = 1) = x }
> class B : A() {
> override fun f(x: Int) = x * 2 // значение по умолчанию наследуется,
> } // переобъявить его нельзя
> ```
>
> **`vararg` без spread принимает массив как один элемент:**
> ```kotlin
> fun show(vararg items: Any) = items.size
>
> fun main() {
> val arr = arrayOf(1, 2, 3)
> println(show(arr)) // 1 — массив целиком как один аргумент
> println(show(*arr)) // 3 — распакован
> }
> ```
>
> **Перегрузка + значения по умолчанию = неоднозначность.** Компилятор выберет наиболее специфичную перегрузку, а не ту, что со значениями по умолчанию:
> ```kotlin
> fun f(x: Int, y: Int = 0) = "два"
> fun f(x: Int) = "один"
>
> fun main() {
> println(f(1)) // "один" — перегрузка без default побеждает
> }
> ```
> Такого сочетания лучше избегать.
Дополнительно: [Лямбды и функциональные типы](Лямбды%20и%20функциональные%20типы.md)
Далее: [Лямбды и функциональные типы](Лямбды%20и%20функциональные%20типы.md)
@@ -0,0 +1,231 @@
---
title: Числа и переполнение
description: Переполнение целых, точность Double, BigDecimal для денег и математические функции
tags:
- kotlin
- stdlib
---
## Переполнение целых
Kotlin, как и Java, **не проверяет переполнение**. При выходе за границы значение «заворачивается»:
```kotlin
println(Int.MAX_VALUE) // 2147483647
println(Int.MAX_VALUE + 1) // -2147483648 — тихо
println(Int.MIN_VALUE - 1) // 2147483647
```
Это не исключение и даже не предупреждение - просто неверный результат.
Границы типов:
```kotlin
println("${Byte.MIN_VALUE}..${Byte.MAX_VALUE}") // -128..127
println("${Int.MIN_VALUE}..${Int.MAX_VALUE}") // -2147483648..2147483647
println("${Long.MIN_VALUE}..${Long.MAX_VALUE}") // -9223372036854775808..9223372036854775807
```
### Где это кусает
Самый частый случай - умножение `Int`, результат которого не влезает:
```kotlin
val ms = 1000 * 60 * 60 * 24 * 30 // -1702967296 — переполнение!
val msOk = 1000L * 60 * 60 * 24 * 30 // 2592000000 — первый операнд Long
println("$ms $msOk")
```
Правило: если промежуточный результат может превысить 2 млрд, делайте `Long` **первый** операнд. Приписать `L` к последнему множителю недостаточно - умножение идёт слева направо.
### Проверяемая арифметика
Явные функции, бросающие исключение при переполнении:
```kotlin
println(Math.addExact(1, 2)) // 3
// println(Math.addExact(Int.MAX_VALUE, 1)) // ArithmeticException
// println(Math.multiplyExact(100000, 100000)) // ArithmeticException
```
Доступны `addExact`, `subtractExact`, `multiplyExact`, `incrementExact`, `toIntExact`. Это функции JDK, в stdlib Kotlin аналогов нет.
## Деление
```kotlin
println(7 / 2) // 3 — целочисленное, дробная часть отброшена
println(-7 / 2) // -3 — округление к нулю, не к меньшему
println(7 % 2) // 1
println(-7 % 2) // -1 — знак от делимого
println(7.0 / 2) // 3.5
```
Деление на целочисленный ноль - исключение, на вещественный - нет:
```kotlin
// println(1 / 0) // ArithmeticException: / by zero
println(1.0 / 0) // Infinity
println(-1.0 / 0) // -Infinity
println(0.0 / 0) // NaN
```
### Остаток для отрицательных
`%` в Kotlin даёт остаток со знаком делимого, что ломает индексацию по кругу:
```kotlin
println(-1 % 3) // -1 — не 2!
println((-1).mod(3)) // 2 — математический модуль
println((-1).rem(3)) // -1 — то же, что %
```
Для циклических индексов нужен `mod`, а не `%`.
## Вещественные числа
`Float` и `Double` следуют IEEE 754 и **не представляют десятичные дроби точно**:
```kotlin
println(0.1 + 0.2) // 0.30000000000000004
println(0.1 + 0.2 == 0.3) // false
println(1.0 - 0.9) // 0.09999999999999998
```
Сравнивать нужно с допуском:
```kotlin
fun almostEqual(a: Double, b: Double, eps: Double = 1e-9) =
kotlin.math.abs(a - b) < eps
println(almostEqual(0.1 + 0.2, 0.3)) // true
```
### NaN и бесконечности
```kotlin
val nan = Double.NaN
println(nan == nan) // false! NaN не равен ничему, включая себя
println(nan.isNaN()) // true — единственный верный способ проверки
println(Double.POSITIVE_INFINITY) // Infinity
println(1.0.isFinite()) // true
println(0.0 == -0.0) // true
```
`NaN` ведёт себя по-разному в операторах и в коллекциях - это редкая, но болезненная ловушка:
```kotlin
println(Double.NaN == Double.NaN) // false — оператор
println(Double.NaN.equals(Double.NaN)) // true — метод equals
println(listOf(Double.NaN).contains(Double.NaN)) // true — через equals
```
## Деньги - BigDecimal
Для денег `Double` использовать нельзя: ошибки округления накапливаются.
```kotlin
import java.math.BigDecimal
import java.math.RoundingMode
fun main() {
val a = BigDecimal("0.1") // только из строки!
val b = BigDecimal("0.2")
println(a + b) // 0.3 — точно
println(a.add(b) == BigDecimal("0.3")) // true
val price = BigDecimal("100.555")
println(price.setScale(2, RoundingMode.HALF_UP)) // 100.56
// операторы перегружены, работают привычно
println(BigDecimal("10") * BigDecimal("3")) // 30
println(BigDecimal("10").divide(BigDecimal("4"))) // 2.5
}
```
Создавать **только из строки**: `BigDecimal(0.1)` подхватит уже испорченное значение `Double`:
```kotlin
import java.math.BigDecimal
fun main() {
println(BigDecimal(0.1)) // 0.1000000000000000055511151231257827...
println(BigDecimal("0.1")) // 0.1
}
```
`BigDecimal.equals` сравнивает и масштаб, поэтому `2.0` и `2.00` не равны:
```kotlin
import java.math.BigDecimal
fun main() {
println(BigDecimal("2.0") == BigDecimal("2.00")) // false
println(BigDecimal("2.0").compareTo(BigDecimal("2.00")) == 0) // true
}
```
Для сравнения значений - `compareTo`, не `==`.
## Математические функции
```kotlin
import kotlin.math.*
fun main() {
println(abs(-5)) // 5
println(max(3, 7)) // 7
println(min(3, 7)) // 3
println(sqrt(16.0)) // 4.0
println(2.0.pow(10)) // 1024.0
println(PI) // 3.141592653589793
println(round(2.5)) // 2.0 — не 3.0! округление к чётному
println(round(3.5)) // 4.0
println(floor(2.9)) // 2.0
println(ceil(2.1)) // 3.0
println(truncate(2.9)) // 2.0
println(2.567.roundToInt()) // 3
println(hypot(3.0, 4.0)) // 5.0
}
```
Пакет `kotlin.math` нужно импортировать - автоматически он не подключается.
Обратите внимание на `round`: при ровной половине он округляет **к ближайшему чётному** (IEEE 754 round-half-even), поэтому `round(2.5)` даёт `2.0`, а `round(3.5)` - `4.0`. Если нужно привычное «половина вверх», берите `roundToInt()`: `2.5.roundToInt()` даёт `3`.
### Ограничение диапазона
```kotlin
println(15.coerceIn(0, 10)) // 10
println((-5).coerceIn(0, 10)) // 0
println(5.coerceAtLeast(0)) // 5
println(15.coerceAtMost(10)) // 10
```
Удобнее, чем `max(0, min(10, x))`.
## Преобразования и потеря данных
```kotlin
import kotlin.math.roundToInt
fun main() {
println(300.toByte()) // 44 — старшие биты отброшены
println(3.99.toInt()) // 3 — отбрасывание, не округление
println((-3.99).toInt()) // -3
println(3.99.roundToInt()) // 4 — настоящее округление
println(Double.NaN.toInt()) // 0 — тихо!
println(1e20.toInt()) // 2147483647 — обрезано до MAX_VALUE
}
```
`toInt()` на `Double` никогда не падает - он тихо даёт неверный результат. Для контроля используйте `roundToInt()` и проверку `isFinite()`.
## Представление и биты
```kotlin
println(255.toString(2)) // 11111111 — в двоичной
println(255.toString(16)) // ff
println("ff".toInt(16)) // 255
println(5.countOneBits()) // 2 — число единичных битов
println(Integer.toBinaryString(-1)) // 32 единицы
```
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `Math.abs(x)` | `kotlin.math.abs(x)` |
> | `Math.pow(2, 10)` | `2.0.pow(10)` |
> | `Integer.MAX_VALUE` | `Int.MAX_VALUE` |
> | `Integer.parseInt(s)` | `s.toInt()` |
> | `Integer.toBinaryString(n)` | `n.toString(2)` |
> | `Math.floorMod(-1, 3)` | `(-1).mod(3)` |
> | неявное `int` → `long` | только явный `toLong()` |
> | `Math.addExact` | то же (из JDK) |
>
> Поведение переполнения, деления и IEEE 754 полностью унаследовано от JVM - здесь Kotlin ничего не меняет и не защищает. Выигрыш только в читаемости: `2.0.pow(10)` вместо `Math.pow(2, 10)`, `coerceIn` вместо вложенных `min`/`max`.
>
> Запрет неявных преобразований - реальная защита: в Java `long total = intA * intB;` переполняется до присваивания, и компилятор молчит. В Kotlin такой код не соберётся без явного `toLong()`.
>
> Сравнить: [Типы данных в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Типы%20данных.md)
> [!warning] Подводные камни
> **`1000 * 60 * 60 * 24 * 30` переполняется** - самая частая реальная ошибка с числами. Все операнды `Int`, результат считается в `Int`, и только потом присваивается в `Long`.
>
> **`Double` для денег даёт расхождения в копейках**, которые накапливаются на больших объёмах. Только `BigDecimal` из строки или целые копейки в `Long`.
>
> **`NaN == NaN` ложно, а `listOf(NaN).contains(NaN)` истинно.** Сортировка и `distinct()` с `NaN` ведут себя неинтуитивно.
>
> **`toInt()` на `Double` обрезает, а `NaN` превращает в 0** - без единого предупреждения.
>
> **`%` для отрицательных чисел даёт отрицательный остаток.** Код `arr[(i - 1) % n]` упадёт на `i = 0`; нужен `arr[(i - 1).mod(n)]`.
>
> **`BigDecimal("2.0") != BigDecimal("2.00")`** - `equals` учитывает масштаб. Сравнивайте через `compareTo`.
Дополнительно: [Типы данных](../Базовый%20синтаксис/Типы%20данных.md)
Далее: [Массивы](Массивы.md)