Added kotlin guide. (WIP) (15.10.26)
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Hello World!
|
||||
description: Первая программа на Kotlin, функция main и запуск через kotlinc
|
||||
tags:
|
||||
- kotlin
|
||||
- syntax
|
||||
---
|
||||
|
||||
```kotlin
|
||||
fun main() {
|
||||
println("Hello World")
|
||||
}
|
||||
```
|
||||
|
||||
Файл `Hello.kt`. Имя файла **не связано** с содержимым - можно назвать как угодно.
|
||||
|
||||
- `fun` - объявление функции
|
||||
- `main` - точка входа, может быть без параметров
|
||||
- `println` - функция из стандартной библиотеки, не метод класса
|
||||
- Точки с запятой не нужны
|
||||
|
||||
## Точка входа
|
||||
Два допустимых варианта:
|
||||
```kotlin
|
||||
fun main() { } // обычно используют этот
|
||||
fun main(args: Array<String>) { } // если нужны аргументы командной строки
|
||||
```
|
||||
Функция `main` должна быть на верхнем уровне файла (top-level), внутри класса её быть не может.
|
||||
|
||||
## Запуск
|
||||
```bash
|
||||
kotlinc Hello.kt -include-runtime -d hello.jar
|
||||
java -jar hello.jar
|
||||
```
|
||||
Или через скриптовый режим, без создания jar:
|
||||
```bash
|
||||
kotlin Hello.kt
|
||||
```
|
||||
Подробнее: [kotlinc](../../Инструментарий/kotlinc.md)
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> В Java `main` обязан лежать внутри класса, быть `public static void` и принимать `String[]`, а имя файла должно совпадать с именем публичного класса ([Hello World! в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Hello%20World!.md)).
|
||||
> В Kotlin всё это не требуется: функции живут прямо в файле.
|
||||
>
|
||||
> На уровне байт-кода класс всё равно появляется - компилятор создаёт `HelloKt` с методом `public static void main(String[])`. Поэтому JVM запускает Kotlin-код без каких-либо адаптеров.
|
||||
> Подробнее: [Kotlin в байт-коде](../../Взаимодействие%20с%20Java/Kotlin%20в%20байт-коде.md)
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> - `fun Main()` или `fun start()` скомпилируется, но JVM точку входа не найдёт - имя строго `main`
|
||||
> - Если в файле есть `package`, запускать надо с полным именем: `java -cp . com.example.HelloKt`
|
||||
> - Две функции `main()` в одном пакете в разных файлах - это нормально, классы-обёртки будут разными (`HelloKt`, `AppKt`)
|
||||
|
||||
Далее: [Типы данных](Типы%20данных.md)
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
title: Операторы и выражения
|
||||
description: Арифметика, равенство == и ===, побитовые операции словами, приоритет операторов
|
||||
tags:
|
||||
- kotlin
|
||||
- syntax
|
||||
---
|
||||
|
||||
В Kotlin почти все операторы - это вызовы функций. `a + b` компилируется в `a.plus(b)`, поэтому операторы можно определять для своих типов ([Операторные функции](../../Функциональный%20Kotlin/Инфиксные%20и%20операторные%20функции.md)).
|
||||
|
||||
## Арифметические
|
||||
| Оператор | Функция | Пример |
|
||||
|---|---|---|
|
||||
| `+` | `plus` | `2 + 3` → 5 |
|
||||
| `-` | `minus` | `5 - 3` → 2 |
|
||||
| `*` | `times` | `2 * 3` → 6 |
|
||||
| `/` | `div` | `7 / 2` → 3 |
|
||||
| `%` | `rem` | `7 % 2` → 1 |
|
||||
|
||||
Целочисленное деление отбрасывает дробную часть:
|
||||
```kotlin
|
||||
println(7 / 2) // 3
|
||||
println(7.0 / 2) // 3.5
|
||||
println(7 / 2.0) // 3.5
|
||||
println(-7 / 2) // -3 — округление к нулю
|
||||
println(-7 % 2) // -1 — знак остатка совпадает со знаком делимого
|
||||
```
|
||||
|
||||
### Инкремент и декремент
|
||||
```kotlin
|
||||
var i = 0
|
||||
println(i++) // 0, потом i = 1
|
||||
println(++i) // 2
|
||||
```
|
||||
Работают только с `var`. Присваивания-операторы (`+=`, `-=`, `*=`, `/=`, `%=`) - тоже.
|
||||
|
||||
## Сравнение
|
||||
`<`, `>`, `<=`, `>=` транслируются в `compareTo`:
|
||||
```kotlin
|
||||
println(2 < 3) // true
|
||||
println("abc" < "abd") // true — лексикографически
|
||||
```
|
||||
Работает для любого типа, реализующего `Comparable`.
|
||||
|
||||
## Равенство: == и ===
|
||||
Это главное место, где Kotlin расходится с Java.
|
||||
|
||||
| Оператор | Что делает |
|
||||
|---|---|
|
||||
| `==` | структурное равенство, вызывает `equals()` |
|
||||
| `===` | ссылочное равенство, сравнивает адреса |
|
||||
| `!=`, `!==` | их отрицания |
|
||||
|
||||
```kotlin
|
||||
val a = "Kotlin"
|
||||
val b = StringBuilder("Kotlin").toString()
|
||||
println(a == b) // true — содержимое совпадает
|
||||
println(a === b) // false — разные объекты
|
||||
```
|
||||
|
||||
`==` безопасен для null: `a == b` разворачивается в `a?.equals(b) ?: (b === null)`.
|
||||
```kotlin
|
||||
val s: String? = null
|
||||
println(s == "text") // false, без NPE
|
||||
println(s == null) // true
|
||||
```
|
||||
|
||||
## Логические
|
||||
```kotlin
|
||||
val r1 = true && false // ленивый И
|
||||
val r2 = true || false // ленивый ИЛИ
|
||||
val r3 = !true // НЕ
|
||||
|
||||
val r4 = true and false // НЕ ленивый — оба операнда вычисляются
|
||||
val r5 = true or false // НЕ ленивый
|
||||
val r6 = true xor true // false
|
||||
```
|
||||
В обычном коде используют `&&` и `||`: они не вычисляют правую часть, если результат уже известен.
|
||||
|
||||
## Побитовые операции
|
||||
Символов `&`, `|`, `^`, `<<`, `>>` для чисел в Kotlin **нет** - только инфиксные функции:
|
||||
```kotlin
|
||||
val a = 0b1100
|
||||
val b = 0b1010
|
||||
println(a and b) // 8 = 0b1000
|
||||
println(a or b) // 14 = 0b1110
|
||||
println(a xor b) // 6 = 0b0110
|
||||
println(a.inv()) // -13 — побитовое НЕ
|
||||
println(1 shl 4) // 16 — сдвиг влево
|
||||
println(16 shr 2) // 4 — сдвиг вправо со знаком
|
||||
println(-16 ushr 2) // беззнаковый сдвиг вправо
|
||||
```
|
||||
Доступны для `Int` и `Long`.
|
||||
|
||||
## Операторы диапазонов и коллекций
|
||||
```kotlin
|
||||
println(5 in 1..10) // true
|
||||
println(5 !in 1..10) // false
|
||||
println("a" in listOf("a","b")) // true — вызывает contains()
|
||||
val list = listOf(1, 2)
|
||||
println(list + 3) // [1, 2, 3] — plus для коллекций
|
||||
```
|
||||
Подробнее: [Циклы и диапазоны](Циклы%20и%20диапазоны.md)
|
||||
|
||||
## Приоритет
|
||||
От высшего к низшему:
|
||||
|
||||
| Уровень | Операторы |
|
||||
|---|---|
|
||||
| постфиксные | `++`, `--`, `.`, `?.`, `!!` |
|
||||
| префиксные | `-`, `+`, `++`, `--`, `!` |
|
||||
| приведение | `as`, `as?` |
|
||||
| умножение | `*`, `/`, `%` |
|
||||
| сложение | `+`, `-` |
|
||||
| диапазон | `..`, `..<` |
|
||||
| инфиксные функции | `and`, `shl`, своя функция `infix` |
|
||||
| проверка принадлежности | `in`, `!in` |
|
||||
| проверка типа | `is`, `!is` |
|
||||
| сравнение | `<`, `>`, `<=`, `>=` |
|
||||
| равенство | `==`, `!=`, `===`, `!==` |
|
||||
| конъюнкция | `&&` |
|
||||
| дизъюнкция | `\|\|` |
|
||||
| elvis | `?:` |
|
||||
| присваивание | `=`, `+=`, … |
|
||||
|
||||
На практике: сомневаетесь - ставьте скобки.
|
||||
|
||||
## Всё является выражением
|
||||
`if`, `when`, `try` возвращают значение:
|
||||
```kotlin
|
||||
val max = if (a > b) a else b
|
||||
val name = when (code) {
|
||||
200 -> "ok"
|
||||
else -> "unknown"
|
||||
}
|
||||
val n = try { text.toInt() } catch (e: NumberFormatException) { 0 }
|
||||
```
|
||||
Подробнее: [Условные конструкции](Условные%20конструкции.md)
|
||||
|
||||
Циклы (`for`, `while`) - **не** выражения, они возвращают `Unit`.
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> | Java | Kotlin |
|
||||
> |---|---|
|
||||
> | `==` для объектов - ссылки | `==` - вызов `equals()` |
|
||||
> | `.equals()` - содержимое | `===` - ссылки |
|
||||
> | `a & b` побитовое И | `a and b` |
|
||||
> | `a >>> b` | `a ushr b` |
|
||||
> | `~a` | `a.inv()` |
|
||||
> | тернарник `c ? a : b` | `if (c) a else b` |
|
||||
> | операторы нельзя перегружать | можно, через `operator fun` |
|
||||
> | `s.equals(t)` падает, если `s` null | `s == t` безопасен |
|
||||
>
|
||||
> Главная ловушка при переходе: в Java `==` на строках сравнивает ссылки и работает «случайно» благодаря [интернированию](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Строки.md). В Kotlin `==` всегда сравнивает содержимое - это и нужно в 99% случаев.
|
||||
>
|
||||
> Сравнить: [Операторы в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Операторы%20и%20выражения.md)
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> **`and` и `or` не ленивые.** Для `Boolean` это рабочий код, но правая часть вычислится всегда:
|
||||
> ```kotlin
|
||||
> fun risky(): Boolean { println("вызвана"); return true }
|
||||
> val a = false && risky() // risky() не вызовется
|
||||
> val b = false and risky() // "вызвана" напечатается
|
||||
> ```
|
||||
>
|
||||
> **Деление на целочисленный ноль - исключение, на вещественный - нет:**
|
||||
> ```kotlin
|
||||
> // println(1 / 0) // ArithmeticException
|
||||
> println(1.0 / 0) // Infinity
|
||||
> println(0.0 / 0) // NaN
|
||||
> println(Double.NaN == Double.NaN) // false! NaN не равен себе
|
||||
> ```
|
||||
>
|
||||
> **Приоритет инфиксных функций ниже арифметики:**
|
||||
> ```kotlin
|
||||
> println(1 shl 2 + 3) // 32, а не 7: это 1 shl (2+3)
|
||||
> println((1 shl 2) + 3) // 7
|
||||
> ```
|
||||
>
|
||||
> **`++` на упакованном значении создаёт новый объект** - в горячем цикле с `Int?` это аллокации. Используйте не-nullable типы.
|
||||
|
||||
Далее: [Условные конструкции](Условные%20конструкции.md)
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
title: Пакеты и импорты
|
||||
description: Объявление пакетов, импорты, псевдонимы и автоматически импортируемые пакеты
|
||||
tags:
|
||||
- kotlin
|
||||
- syntax
|
||||
---
|
||||
|
||||
## Пакет
|
||||
Объявляется первой строкой файла:
|
||||
```kotlin
|
||||
package com.example.app
|
||||
|
||||
fun helper() { }
|
||||
class Service
|
||||
```
|
||||
Полное имя функции - `com.example.app.helper`.
|
||||
|
||||
Отличие от Java: **структура каталогов не обязана совпадать с пакетом**, и имя файла не связано с содержимым. Компилятор не возражает. На практике соглашение соблюдают: иначе навигация по проекту ломается.
|
||||
|
||||
Файл без `package` попадает в корневой пакет (default package).
|
||||
|
||||
## Содержимое файла
|
||||
В одном файле может быть сколько угодно объявлений верхнего уровня:
|
||||
```kotlin
|
||||
package com.example
|
||||
|
||||
const val VERSION = "1.0"
|
||||
|
||||
fun parse(text: String): Int = text.toInt()
|
||||
|
||||
class User(val name: String)
|
||||
class Admin(val name: String)
|
||||
|
||||
private fun internalHelper() { } // видна только в этом файле
|
||||
```
|
||||
Никаких `public class` на файл - ограничения «один публичный класс на файл» в Kotlin нет.
|
||||
|
||||
## Импорты
|
||||
```kotlin
|
||||
import com.example.app.Service // конкретный класс
|
||||
import com.example.app.helper // функция верхнего уровня
|
||||
import com.example.app.* // всё из пакета
|
||||
import com.example.app.User.Companion.create // член companion object
|
||||
```
|
||||
Импортировать можно не только типы, но и функции, свойства, enum-константы - всё, что объявлено на верхнем уровне или как статический член.
|
||||
|
||||
```kotlin
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.sqrt
|
||||
|
||||
fun main() {
|
||||
println(sqrt(PI)) // 1.7724538509055159
|
||||
}
|
||||
```
|
||||
|
||||
## Псевдонимы - as
|
||||
Когда имена конфликтуют или слишком длинные:
|
||||
```kotlin
|
||||
import com.example.data.User as DataUser
|
||||
import com.example.api.User as ApiUser
|
||||
|
||||
fun convert(u: DataUser): ApiUser = ApiUser(u.name)
|
||||
```
|
||||
Псевдоним действует только в своём файле. Если нужно имя на весь модуль - это [typealias](../Базовые%20типы%20ООП/Typealias.md).
|
||||
|
||||
## Импортируются автоматически
|
||||
Эти пакеты доступны без `import`:
|
||||
|
||||
| Пакет | Что содержит |
|
||||
|---|---|
|
||||
| `kotlin.*` | `Int`, `String`, `Any`, базовые функции |
|
||||
| `kotlin.annotation.*` | аннотации |
|
||||
| `kotlin.collections.*` | `List`, `Map`, `listOf`, операции |
|
||||
| `kotlin.comparisons.*` | `compareBy`, `maxOf` |
|
||||
| `kotlin.io.*` | `println`, `readln` |
|
||||
| `kotlin.ranges.*` | диапазоны, `step`, `downTo` |
|
||||
| `kotlin.sequences.*` | `Sequence`, `sequenceOf` |
|
||||
| `kotlin.text.*` | операции со строками |
|
||||
| `java.lang.*` | только на JVM |
|
||||
| `kotlin.jvm.*` | только на JVM |
|
||||
|
||||
Поэтому `println`, `listOf`, `Int` работают из коробки. А `kotlin.math.sqrt` - нет, его импортируют явно.
|
||||
|
||||
## Видимость на верхнем уровне
|
||||
```kotlin
|
||||
public fun api() { } // видна везде (public — по умолчанию)
|
||||
internal fun moduleOnly() { } // видна в своём модуле
|
||||
private fun fileOnly() { } // видна только в этом файле
|
||||
```
|
||||
`protected` на верхнем уровне запрещён - нет класса, от которого наследоваться.
|
||||
Подробнее: [Модификаторы видимости](../../ООП/Модификаторы%20видимости.md)
|
||||
|
||||
## Имя класса-обёртки
|
||||
Функции верхнего уровня попадают в класс, названный по файлу: `Utils.kt` → класс `UtilsKt`. Имя можно задать:
|
||||
```kotlin
|
||||
@file:JvmName("Utils")
|
||||
package com.example
|
||||
|
||||
fun helper() { } // из Java: Utils.helper()
|
||||
```
|
||||
Аннотации уровня файла ставятся **до** `package`. Подробнее: [Kotlin из Java](../../Взаимодействие%20с%20Java/Kotlin%20из%20Java.md)
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> | Java | Kotlin |
|
||||
> |---|---|
|
||||
> | каталоги обязаны совпадать с пакетом | не обязаны (но так принято) |
|
||||
> | имя файла = имя публичного класса | имя файла произвольно |
|
||||
> | один публичный класс на файл | сколько угодно объявлений |
|
||||
> | `import static` для статики | обычный `import` для всего |
|
||||
> | псевдонимов нет | `import ... as Name` |
|
||||
> | автоимпорт только `java.lang` | десять пакетов `kotlin.*` |
|
||||
> | `package-private` по умолчанию | `public` по умолчанию |
|
||||
>
|
||||
> Последняя строка важна: в Java класс без модификатора виден только своему пакету, в Kotlin - всем. Для сокрытия внутренностей библиотеки используют `internal`.
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> **Нет аналога package-private.** `internal` ограничивает *модулем* (единицей компиляции Gradle), а не пакетом. Разделить код на пакеты и спрятать их друг от друга внутри одного модуля нельзя - только разносить по модулям.
|
||||
>
|
||||
> **`import *` не затягивает вложенные пакеты.** `import com.example.*` не даёт доступа к `com.example.data.User`.
|
||||
>
|
||||
> **Конфликт импортов - ошибка компиляции, а не предупреждение.** Два `User` из разных пакетов требуют `as` или полного имени.
|
||||
>
|
||||
> **Функции с одинаковым именем в разных файлах одного пакета конфликтуют.** Два `private fun helper()` в разных файлах - нормально, а два `fun helper()` с одной сигнатурой - ошибка: оба попадают в один пакет.
|
||||
>
|
||||
> **`@file:JvmName` не действует, если имя файла совпадает с именем класса в нём.** Коллизия имён вызовет ошибку компиляции.
|
||||
|
||||
Дополнительно: [Структура проекта](../../Инструментарий/Структура%20проекта.md)
|
||||
Далее: [Null-безопасность](../Null-безопасность/Null-безопасность.md)
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
title: Переменные
|
||||
description: val и var, вывод типов, const val, lateinit и области видимости
|
||||
tags:
|
||||
- kotlin
|
||||
- syntax
|
||||
---
|
||||
|
||||
Два ключевых слова:
|
||||
- `val` - **value**, значение присваивается один раз (read-only)
|
||||
- `var` - **variable**, значение можно менять
|
||||
|
||||
```kotlin
|
||||
val name = "Kotlin"
|
||||
var count = 0
|
||||
count++ // ok
|
||||
// name = "Java" // ошибка компиляции: val cannot be reassigned
|
||||
```
|
||||
|
||||
Правило стиля: по умолчанию `val`, `var` - только когда переменная действительно меняется. Это не формальность: с `val` работают [умные приведения](../Null-безопасность/Умные%20приведения.md), а код становится предсказуемее.
|
||||
|
||||
## Объявление
|
||||
```kotlin
|
||||
val a: Int = 1 // тип указан явно
|
||||
val b = 1 // тип выведен: Int
|
||||
val c: Int // без инициализации — тип обязателен
|
||||
c = 2 // присвоить можно позже, но только один раз
|
||||
```
|
||||
Тип ставится **после** имени через двоеточие.
|
||||
|
||||
## Вывод типов
|
||||
Компилятор выводит тип из значения:
|
||||
```kotlin
|
||||
val n = 42 // Int
|
||||
val l = 42L // Long
|
||||
val s = "text" // String
|
||||
val list = listOf(1, 2) // List<Int>
|
||||
val mixed = listOf(1, "a") // List<Any>
|
||||
```
|
||||
Вывод работает только там, где есть что выводить. У свойств класса и возвращаемых значений публичных функций тип лучше писать явно - это часть контракта.
|
||||
|
||||
## val - не значит immutable
|
||||
`val` запрещает переприсваивание ссылки, но не меняет сам объект:
|
||||
```kotlin
|
||||
val list = mutableListOf(1, 2)
|
||||
list.add(3) // ok — меняем объект
|
||||
println(list) // [1, 2, 3]
|
||||
// list = mutableListOf() // ошибка — меняем ссылку
|
||||
```
|
||||
Неизменяемость данных - это выбор типа (`List` вместо `MutableList`), а не `val`. Подробнее: [Изменяемость коллекций](../../Коллекции/Изменяемость.md)
|
||||
|
||||
## val с геттером
|
||||
`val` может вычисляться при каждом обращении:
|
||||
```kotlin
|
||||
val now: Long
|
||||
get() = System.currentTimeMillis()
|
||||
```
|
||||
Это уже свойство, а не поле. Подробнее: [Свойства](../../ООП/Свойства.md)
|
||||
|
||||
## Константы - const val
|
||||
```kotlin
|
||||
const val MAX = 100 // верхний уровень файла
|
||||
const val GREETING = "Hello"
|
||||
|
||||
object Config {
|
||||
const val TIMEOUT = 30 // или внутри object
|
||||
}
|
||||
```
|
||||
Требования к `const val`:
|
||||
- только `String` или примитивный тип
|
||||
- значение известно на этапе компиляции
|
||||
- только на верхнем уровне, в `object` или в `companion object`
|
||||
- нельзя у локальной переменной и нельзя с кастомным геттером
|
||||
|
||||
Значение подставляется в место использования на этапе компиляции (инлайнится), обращения к полю в рантайме нет.
|
||||
|
||||
```kotlin
|
||||
val runtime = computeLimit() // вычисляется при запуске
|
||||
const val compiled = 100 // подставлено компилятором
|
||||
```
|
||||
|
||||
## lateinit
|
||||
Для `var`, который точно будет проинициализирован, но не в момент объявления (DI, настройка в тестах):
|
||||
```kotlin
|
||||
class Service {
|
||||
lateinit var client: HttpClient
|
||||
|
||||
fun setup() {
|
||||
client = HttpClient()
|
||||
}
|
||||
|
||||
fun use() {
|
||||
if (::client.isInitialized) client.send()
|
||||
}
|
||||
}
|
||||
```
|
||||
Ограничения `lateinit`:
|
||||
- только `var`
|
||||
- тип не nullable
|
||||
- не примитивный тип (`Int`, `Double`, … запрещены)
|
||||
- нельзя у локальных переменных
|
||||
- нельзя с кастомным геттером/сеттером
|
||||
|
||||
Обращение до инициализации - `UninitializedPropertyAccessException`.
|
||||
|
||||
Альтернатива для ленивых вычислений - `by lazy`, он работает с `val`:
|
||||
```kotlin
|
||||
val config: Config by lazy { loadConfig() } // вычислится при первом обращении
|
||||
```
|
||||
Подробнее: [Свойства](../../ООП/Свойства.md)
|
||||
|
||||
## Области видимости
|
||||
```kotlin
|
||||
const val TOP = 1 // весь файл и, если public, другие файлы
|
||||
|
||||
fun demo() {
|
||||
val local = 2 // только тело функции
|
||||
if (local > 1) {
|
||||
val inner = 3 // только тело if
|
||||
}
|
||||
// println(inner) // ошибка: не видна
|
||||
}
|
||||
```
|
||||
Вложенная переменная может скрыть внешнюю - компилятор выдаст предупреждение `name shadowed`.
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> | Java | Kotlin |
|
||||
> |---|---|
|
||||
> | `final int x = 1;` | `val x = 1` |
|
||||
> | `int x = 1;` | `var x = 1` |
|
||||
> | `var x = 1;` (только локально, с Java 10) | вывод типов везде, включая свойства класса |
|
||||
> | `static final int MAX = 100;` | `const val MAX = 100` |
|
||||
> | мутабельность по умолчанию | иммутабельность предпочтительна |
|
||||
> | `int x;` - значение по умолчанию 0 | неинициализированную переменную читать нельзя |
|
||||
>
|
||||
> Важное следствие: у свойств класса в Kotlin **нет значений по умолчанию**. В Java поле `int count;` автоматически 0, в Kotlin `var count: Int` без инициализатора не скомпилируется.
|
||||
>
|
||||
> Сравнить: [Переменные в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Переменные.md)
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> **`val` не делает объект неизменяемым** - см. пример с `MutableList` выше. Частая ошибка на собеседованиях.
|
||||
>
|
||||
> **`lateinit` прячет ошибку до рантайма.** Если забыли вызвать `setup()`, падение случится в момент использования, далеко от причины. Там, где можно, лучше конструктор или `by lazy`.
|
||||
>
|
||||
> **Вывод типов может дать не тот тип, что ожидали:**
|
||||
> ```kotlin
|
||||
> val x = 1 / 2 // 0, а не 0.5 — оба операнда Int
|
||||
> val y = 1.0 / 2 // 0.5
|
||||
> ```
|
||||
>
|
||||
> **`const val` зашивается в вызывающий код.** Если константа лежит в библиотеке и её значение изменилось, зависимый модуль нужно перекомпилировать - иначе он продолжит использовать старое значение.
|
||||
|
||||
Дополнительно: [Свойства](../../ООП/Свойства.md)
|
||||
Далее: [Операторы и выражения](Операторы%20и%20выражения.md)
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: Строковые шаблоны и вывод
|
||||
description: Интерполяция строк, raw strings, trimIndent, println и форматированный вывод
|
||||
tags:
|
||||
- kotlin
|
||||
- syntax
|
||||
---
|
||||
|
||||
## Шаблоны строк
|
||||
Вместо конкатенации - подстановка значений прямо в строку:
|
||||
```kotlin
|
||||
val name = "Kotlin"
|
||||
val version = 2.4
|
||||
|
||||
println("Привет, $name!") // Привет, Kotlin!
|
||||
println("Версия: $version") // Версия: 2.4
|
||||
println("Длина имени: ${name.length}") // Длина имени: 6
|
||||
println("Сумма: ${2 + 3}") // Сумма: 5
|
||||
```
|
||||
|
||||
Правила:
|
||||
- `$имя` - для простой переменной
|
||||
- `${выражение}` - для всего остального: вызовов, свойств, арифметики
|
||||
- вызывается `toString()` подставляемого значения
|
||||
- `null` подставляется как текст `null`
|
||||
|
||||
```kotlin
|
||||
val x: String? = null
|
||||
println("значение: $x") // значение: null
|
||||
```
|
||||
|
||||
Фигурные скобки можно ставить всегда - многие так и делают для единообразия.
|
||||
|
||||
## Экранирование
|
||||
```kotlin
|
||||
println("цена: \$100") // цена: $100
|
||||
println("перенос\nстроки")
|
||||
println("табуляция\tтут")
|
||||
println("кавычка \" внутри")
|
||||
println("слеш \\ внутри")
|
||||
println("символ \u00A9") // ©
|
||||
```
|
||||
Поддерживаются `\t`, `\b`, `\n`, `\r`, `\'`, `\"`, `\\`, `\$`, `\uXXXX`.
|
||||
|
||||
## Raw strings
|
||||
Три кавычки - никакого экранирования, переносы сохраняются как есть:
|
||||
```kotlin
|
||||
val path = """C:\Users\name\file.txt""" // слеши не экранируются
|
||||
val json = """
|
||||
{
|
||||
"name": "Kotlin",
|
||||
"stable": true
|
||||
}
|
||||
"""
|
||||
```
|
||||
Удобно для regex, JSON, SQL, HTML.
|
||||
|
||||
Шаблоны `$` в raw strings **работают**:
|
||||
```kotlin
|
||||
val user = "admin"
|
||||
val query = """SELECT * FROM users WHERE name = '$user'"""
|
||||
```
|
||||
Чтобы вставить настоящий `$`, используют подстановку символа:
|
||||
```kotlin
|
||||
val price = """цена: ${'$'}100""" // цена: $100
|
||||
```
|
||||
|
||||
## trimIndent и trimMargin
|
||||
Raw string сохраняет отступы исходного кода - их убирают явно:
|
||||
```kotlin
|
||||
val text = """
|
||||
Первая строка
|
||||
Вторая строка
|
||||
""".trimIndent()
|
||||
// "Первая строка\nВторая строка" — общий отступ убран,
|
||||
// пустые первая и последняя строки удалены
|
||||
|
||||
val bar = """
|
||||
|Первая
|
||||
|Вторая
|
||||
""".trimMargin() // по умолчанию маркер |
|
||||
```
|
||||
`trimIndent()` вычисляет минимальный общий отступ непустых строк и срезает его у всех.
|
||||
|
||||
## println и print
|
||||
```kotlin
|
||||
print("без переноса")
|
||||
println("с переносом")
|
||||
println() // пустая строка
|
||||
println(listOf(1, 2, 3)) // [1, 2, 3]
|
||||
```
|
||||
Это функции стандартной библиотеки, доступны без импорта. Под капотом - `System.out`.
|
||||
|
||||
## Форматированный вывод
|
||||
Kotlin использует механизм форматирования Java:
|
||||
```kotlin
|
||||
import java.util.Locale
|
||||
|
||||
fun main() {
|
||||
println("%05d".format(42)) // 00042
|
||||
println("%-10s|".format("left")) // left |
|
||||
println(String.format("%s = %d", "x", 5)) // x = 5
|
||||
|
||||
// вещественные числа и разряды зависят от локали — её лучше задавать явно
|
||||
println("%.2f".format(Locale.US, 3.14159)) // 3.14
|
||||
println("%,d".format(Locale.US, 1234567)) // 1,234,567
|
||||
}
|
||||
```
|
||||
|
||||
> [!warning] Локаль по умолчанию
|
||||
> `format` без явной локали берёт системную. На русской локали получится `3,14` и `1 234 567` - с запятой и неразрывными пробелами. Если строка идёт в JSON, SQL или лог, это ломает парсинг. Для машинных форматов всегда передавайте `Locale.US` (или `Locale.ROOT`).
|
||||
|
||||
Основные спецификаторы:
|
||||
|
||||
| Спецификатор | Значение |
|
||||
|---|---|
|
||||
| `%d` | целое число |
|
||||
| `%f` | вещественное |
|
||||
| `%.Nf` | вещественное с N знаками |
|
||||
| `%s` | строка (через `toString()`) |
|
||||
| `%b` | boolean |
|
||||
| `%x` | шестнадцатеричное |
|
||||
| `%e` | экспоненциальная запись |
|
||||
| `%n` | перенос строки платформы |
|
||||
| `%%` | символ процента |
|
||||
|
||||
Флаги ширины: `%10d` - по правому краю, `%-10d` - по левому, `%010d` - с ведущими нулями.
|
||||
|
||||
## Чтение ввода
|
||||
```kotlin
|
||||
val line = readLine() // String? — null при конце потока
|
||||
val text = readln() // String — бросает исключение при EOF
|
||||
val n = readln().toInt() // падает на некорректном вводе
|
||||
val safe = readln().toIntOrNull() ?: 0 // безопасный разбор
|
||||
```
|
||||
`readln()` появился в Kotlin 1.6 и удобнее, когда ввод гарантирован.
|
||||
|
||||
## Конкатенация
|
||||
Работает, но в шаблонах почти не нужна:
|
||||
```kotlin
|
||||
println("Hello" + " " + "world") // Hello world
|
||||
println("Hello" + 5 + 10) // Hello510 — слева направо
|
||||
println("Hello" + (5 + 10)) // Hello15
|
||||
```
|
||||
Важно: складывать можно только когда **слева** строка. У `Int` нет `plus(String)`, поэтому в Kotlin не скомпилируется то, что в Java работает:
|
||||
```kotlin
|
||||
// println(5 + 10 + "Hello") // ошибка: Int.plus ожидает Int, а не String
|
||||
println("${5 + 10}Hello") // 15Hello — через шаблон
|
||||
```
|
||||
Для сборки строки в цикле - `StringBuilder` или `buildString`:
|
||||
```kotlin
|
||||
val result = buildString {
|
||||
repeat(3) { append("ха") }
|
||||
}
|
||||
println(result) // хахаха
|
||||
```
|
||||
Подробнее: [Строки](../Работа%20с%20данными/Строки.md)
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> | Java | Kotlin |
|
||||
> |---|---|
|
||||
> | `System.out.println(x)` | `println(x)` |
|
||||
> | `"a" + x + "b"` | `"a${x}b"` |
|
||||
> | `String.format("%d", n)` | `"%d".format(n)` |
|
||||
> | text blocks `"""` (Java 15+) | raw strings `"""` + шаблоны |
|
||||
> | `.stripIndent()` | `.trimIndent()` |
|
||||
> | `new Scanner(System.in)` | `readln()` |
|
||||
>
|
||||
> Шаблоны - не просто синтаксический сахар: компилятор собирает их через `StringBuilder` или `invokedynamic`, то есть без промежуточных строк. Писать `"a" + b + "c"` ради производительности не нужно.
|
||||
>
|
||||
> Сравнить: [Форматированный вывод в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Форматированный%20вывод.md)
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> **`$` перед словом всегда считается шаблоном.** Строка `"цена: $100"` скомпилируется (цифра не может начинать имя), а `"$name_suffix"` - нет: Kotlin ищет переменную `name_suffix`. Нужны скобки: `"${name}_suffix"`.
|
||||
>
|
||||
> **`trimIndent()` чувствителен к смешению табов и пробелов** - отступ считается по символам, таб и четыре пробела для него разные вещи.
|
||||
>
|
||||
> **`$` в regex внутри raw string.** `"""^\d+$"""` работает, потому что после `$` идёт конец строки. Но `"""$\d"""` компилятор попытается разобрать как шаблон.
|
||||
>
|
||||
> **Шаблон вызывает `toString()` у массива, а не его содержимое:**
|
||||
> ```kotlin
|
||||
> val arr = arrayOf(1, 2, 3)
|
||||
> println("$arr") // [Ljava.lang.Object;@1b6d3586
|
||||
> println(arr.contentToString()) // [1, 2, 3]
|
||||
> println("${arr.joinToString()}") // 1, 2, 3
|
||||
> ```
|
||||
>
|
||||
> **`readLine()` возвращает `String?`.** Вызов `readLine().toInt()` не скомпилируется - нужен `readln()` или обработка null.
|
||||
|
||||
Дополнительно: [Строки](../Работа%20с%20данными/Строки.md)
|
||||
Далее: [Пакеты и импорты](Пакеты%20и%20импорты.md)
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
title: Типы данных
|
||||
description: Система типов Kotlin, числа, boolean, Char и отсутствие примитивов на уровне языка
|
||||
tags:
|
||||
- kotlin
|
||||
- types
|
||||
---
|
||||
|
||||
В Kotlin: статическая типизация, сильная, с выводом типов.
|
||||
|
||||
Главное отличие от Java: **на уровне языка примитивов нет**. Всё - объект, у всего есть методы.
|
||||
```kotlin
|
||||
val x: Int = 42
|
||||
println(x.toString()) // у числа есть методы
|
||||
println(42.coerceIn(0, 10)) // 10
|
||||
```
|
||||
Но в байт-коде `Int` компилируется в `int`, если это возможно - производительность не теряется. Подробнее в [Kotlin в байт-коде](../../Взаимодействие%20с%20Java/Kotlin%20в%20байт-коде.md).
|
||||
|
||||
## Иерархия типов
|
||||
```mermaid
|
||||
graph TD
|
||||
Any["Any — корень всех non-null типов"]
|
||||
Any --> Number
|
||||
Any --> Char
|
||||
Any --> Boolean
|
||||
Any --> String
|
||||
Any --> Other["классы, интерфейсы, ..."]
|
||||
Number --> Byte
|
||||
Number --> Short
|
||||
Number --> Int
|
||||
Number --> Long
|
||||
Number --> Float
|
||||
Number --> Double
|
||||
Nothing["Nothing — наследник всех типов"]
|
||||
```
|
||||
`Any`, `Unit` и `Nothing` - особые типы, им посвящена отдельная заметка: [Any, Unit, Nothing](../Базовые%20типы%20ООП/Any,%20Unit,%20Nothing.md)
|
||||
|
||||
## Числовые типы
|
||||
| Тип | Размер, бит | Диапазон |
|
||||
|---|---|---|
|
||||
| `Byte` | 8 | −128 … 127 |
|
||||
| `Short` | 16 | −32 768 … 32 767 |
|
||||
| `Int` | 32 | ≈ ±2 млрд |
|
||||
| `Long` | 64 | ≈ ±9,2 · 10¹⁸ |
|
||||
| `Float` | 32 | ≈ 6-7 значащих цифр |
|
||||
| `Double` | 64 | ≈ 15-16 значащих цифр |
|
||||
|
||||
Все наследуются от `Number`, но **не** друг от друга: `Int` не является подтипом `Long`.
|
||||
|
||||
### Литералы
|
||||
```kotlin
|
||||
val i = 42 // Int по умолчанию
|
||||
val l = 42L // Long — суффикс L
|
||||
val d = 42.0 // Double по умолчанию
|
||||
val f = 42.0f // Float — суффикс f
|
||||
val hex = 0xFF // 255
|
||||
val bin = 0b1010 // 10
|
||||
val big = 1_000_000 // подчёркивания для читаемости
|
||||
```
|
||||
Восьмеричных литералов (`017` в Java) в Kotlin **нет**.
|
||||
|
||||
## Беззнаковые типы
|
||||
`UByte`, `UShort`, `UInt`, `ULong` - то, чего в Java нет совсем.
|
||||
```kotlin
|
||||
val u: UInt = 4_000_000_000u // суффикс u
|
||||
println(UInt.MAX_VALUE) // 4294967295
|
||||
```
|
||||
Нужны редко: работа с бинарными форматами, протоколами, хешами.
|
||||
|
||||
## Boolean
|
||||
```kotlin
|
||||
val yes: Boolean = true
|
||||
val both = yes && other() // ленивые операторы, как в Java
|
||||
val eager = yes and other() // не ленивый: other() вызовется всегда
|
||||
```
|
||||
|
||||
## Char
|
||||
```kotlin
|
||||
val c: Char = 'A'
|
||||
println(c.code) // 65 — числовой код
|
||||
println(c + 1) // B — Char + Int даёт Char
|
||||
println(c.isLetter()) // true
|
||||
```
|
||||
`Char` **не является числом**: `val n: Int = 'A'` не скомпилируется, нужен `'A'.code`.
|
||||
Подробнее: [Char](../Работа%20с%20данными/Char.md)
|
||||
|
||||
## Преобразования
|
||||
Неявных расширяющих преобразований нет вообще - только явные вызовы:
|
||||
```kotlin
|
||||
val i = 42
|
||||
val l: Long = i.toLong() // обязательно явно
|
||||
val b = i.toByte()
|
||||
val d = i.toDouble()
|
||||
```
|
||||
Доступны `toByte()`, `toShort()`, `toInt()`, `toLong()`, `toFloat()`, `toDouble()`, `toChar()`.
|
||||
|
||||
## Строки
|
||||
`String` - неизменяемый тип, элементы доступны по индексу:
|
||||
```kotlin
|
||||
val s = "Kotlin"
|
||||
println(s[0]) // K
|
||||
println(s.length) // 6
|
||||
```
|
||||
Подробнее: [Строки](../Работа%20с%20данными/Строки.md)
|
||||
|
||||
## Nullable-типы
|
||||
Любой тип имеет nullable-вариант с `?`:
|
||||
```kotlin
|
||||
// val a: Int = null // ошибка компиляции
|
||||
val b: Int? = null // ok
|
||||
```
|
||||
Это ядро языка, ему посвящён отдельный раздел: [Null-безопасность](../Null-безопасность/Null-безопасность.md)
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> | Java | Kotlin |
|
||||
> |---|---|
|
||||
> | `int` и `Integer` - разные типы | только `Int`, компилятор сам выбирает представление |
|
||||
> | автоупаковка неявная | упаковка скрыта, в коде не видна |
|
||||
> | `long x = intValue;` работает | нужен `intValue.toLong()` |
|
||||
> | `char` - числовой тип | `Char` - отдельный тип, не число |
|
||||
> | нет беззнаковых типов | `UInt`, `ULong`, … |
|
||||
> | `017` - восьмеричный литерал | восьмеричных литералов нет |
|
||||
>
|
||||
> Сравнить: [Типы данных в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Типы%20данных.md)
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> **Переполнение не проверяется**, как и в Java:
|
||||
> ```kotlin
|
||||
> println(Int.MAX_VALUE + 1) // -2147483648
|
||||
> ```
|
||||
>
|
||||
> **Равенство упакованных значений.** Если тип nullable, значение упаковано, и `===` сравнивает ссылки:
|
||||
> ```kotlin
|
||||
> val a: Int = 127
|
||||
> val b: Int? = a
|
||||
> val c: Int? = a
|
||||
> println(b == c) // true — сравнение значений
|
||||
> println(b === c) // true — кеш Integer для -128..127
|
||||
>
|
||||
> val x: Int? = 1000
|
||||
> val y: Int? = 1000
|
||||
> println(x == y) // true
|
||||
> println(x === y) // false — за пределами кеша это разные объекты
|
||||
> ```
|
||||
> Вывод: для чисел всегда `==`, никогда `===`.
|
||||
>
|
||||
> **`Float` и `Double` не точны**, это стандарт IEEE 754:
|
||||
> ```kotlin
|
||||
> println(0.1 + 0.2) // 0.30000000000000004
|
||||
> ```
|
||||
> Для денег - `java.math.BigDecimal`.
|
||||
|
||||
Дополнительно: [Числа и переполнение](../Работа%20с%20данными/Числа%20и%20переполнение.md)
|
||||
Далее: [Переменные](Переменные.md)
|
||||
@@ -0,0 +1,175 @@
|
||||
---
|
||||
title: Условные конструкции
|
||||
description: if и when как выражения, исчерпывающий when, отсутствие тернарного оператора и switch
|
||||
tags:
|
||||
- kotlin
|
||||
- syntax
|
||||
---
|
||||
|
||||
В Kotlin две конструкции ветвления: `if` и `when`. Обе могут работать и как инструкция, и как **выражение**, возвращающее значение.
|
||||
|
||||
## if как инструкция
|
||||
```kotlin
|
||||
if (x > 0) {
|
||||
println("положительное")
|
||||
} else if (x < 0) {
|
||||
println("отрицательное")
|
||||
} else {
|
||||
println("ноль")
|
||||
}
|
||||
```
|
||||
Условие обязано быть `Boolean`. Число или null туда подставить нельзя - компилятор не пропустит.
|
||||
|
||||
## if как выражение
|
||||
```kotlin
|
||||
val max = if (a > b) a else b
|
||||
|
||||
val label = if (x > 0) {
|
||||
log("positive")
|
||||
"плюс" // последнее выражение блока — результат
|
||||
} else {
|
||||
"минус"
|
||||
}
|
||||
```
|
||||
Правила:
|
||||
- в режиме выражения ветка `else` **обязательна** - иначе значение не определено
|
||||
- результат блока - его последнее выражение
|
||||
- тип результата - общий супертип всех ветвей
|
||||
|
||||
```kotlin
|
||||
val r = if (flag) 1 else "text" // тип Any — общий супертип
|
||||
```
|
||||
|
||||
Тернарного оператора `?:` в смысле Java в Kotlin нет: его роль играет `if`. Оператор `?:` в Kotlin - это **elvis**, у него другая задача ([Операторы null-безопасности](../Null-безопасность/Операторы%20null-безопасности.md)).
|
||||
|
||||
## when - замена switch
|
||||
Две формы. С аргументом:
|
||||
```kotlin
|
||||
when (code) {
|
||||
200 -> println("ok")
|
||||
404 -> println("not found")
|
||||
500, 502, 503 -> println("ошибка сервера") // несколько значений в одной ветке
|
||||
else -> println("неизвестно")
|
||||
}
|
||||
```
|
||||
Без аргумента - вместо цепочки `if/else if`:
|
||||
```kotlin
|
||||
when {
|
||||
x < 0 -> println("отрицательное")
|
||||
x == 0 -> println("ноль")
|
||||
else -> println("положительное")
|
||||
}
|
||||
```
|
||||
|
||||
Ветки проверяются **сверху вниз**, выполняется первая подошедшая. Никакого `break` не нужно - провала в следующую ветку (fall-through) в Kotlin не бывает.
|
||||
|
||||
## when как выражение
|
||||
```kotlin
|
||||
val name = when (code) {
|
||||
200 -> "ok"
|
||||
404 -> "not found"
|
||||
else -> "unknown"
|
||||
}
|
||||
```
|
||||
В режиме выражения `when` обязан быть **исчерпывающим**: либо есть `else`, либо компилятор видит, что перечислены все варианты.
|
||||
|
||||
## Условия в ветках when
|
||||
Не только константы:
|
||||
```kotlin
|
||||
when (x) {
|
||||
in 1..9 -> "одна цифра" // диапазон
|
||||
in listOf(10, 20, 30) -> "круглое" // коллекция
|
||||
!in 0..100 -> "вне диапазона"
|
||||
else -> "прочее"
|
||||
}
|
||||
|
||||
when (value) {
|
||||
is String -> value.length // проверка типа + умное приведение
|
||||
is Int -> value + 1
|
||||
is List<*> -> value.size
|
||||
else -> 0
|
||||
}
|
||||
|
||||
when (x) {
|
||||
computeLimit() -> "предел" // произвольное выражение
|
||||
else -> "нет"
|
||||
}
|
||||
```
|
||||
После `is` переменная автоматически приводится к проверенному типу - см. [Умные приведения](../Null-безопасность/Умные%20приведения.md).
|
||||
|
||||
## Исчерпывающий when без else
|
||||
С `enum` и `sealed` компилятор сам проверяет полноту:
|
||||
```kotlin
|
||||
enum class State { LOADING, READY, ERROR }
|
||||
|
||||
fun describe(s: State) = when (s) {
|
||||
State.LOADING -> "загрузка"
|
||||
State.READY -> "готово"
|
||||
State.ERROR -> "ошибка"
|
||||
} // else не нужен — все варианты перечислены
|
||||
```
|
||||
Добавили `State.EMPTY` - код **перестанет компилироваться** во всех таких `when`. Это главная причина предпочитать `sealed` и `enum` строкам и числам: компилятор показывает каждое место, которое нужно обновить.
|
||||
|
||||
Подробнее: [Enum class](../Базовые%20типы%20ООП/Enum%20class.md), [Sealed class и interface](../Базовые%20типы%20ООП/Sealed%20class%20и%20interface.md)
|
||||
|
||||
## Переменная в when
|
||||
Аргумент можно объявить прямо в заголовке - он будет виден только внутри:
|
||||
```kotlin
|
||||
when (val response = request()) {
|
||||
is Success -> response.data
|
||||
is Failure -> response.error
|
||||
}
|
||||
```
|
||||
Удобно, когда `request()` дорогой: вызов происходит один раз.
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> | Java | Kotlin |
|
||||
> |---|---|
|
||||
> | `switch` только по числам, строкам, enum | `when` по любым значениям и условиям |
|
||||
> | нужен `break`, иначе fall-through | провала нет, `break` не нужен |
|
||||
> | `c ? a : b` | `if (c) a else b` |
|
||||
> | `if` - только инструкция | `if` - ещё и выражение |
|
||||
> | `switch` не проверяет полноту (до pattern matching в Java 21) | `when` по enum/sealed проверяется компилятором |
|
||||
>
|
||||
> `when` ближе всего к `switch`-выражению из Java 14+ и pattern matching из Java 21, но появился в Kotlin задолго до них и покрывает больше случаев.
|
||||
>
|
||||
> Сравнить: [Условные конструкции в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Условные%20конструкции%20и%20операторы.md)
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> **Полнота проверяется и у `when`-инструкции - но только для `enum` и `sealed`.** С Kotlin 2.0 пропуск ветки - ошибка компиляции даже когда результат не используется:
|
||||
> ```kotlin
|
||||
> fun handle(s: State) {
|
||||
> when (s) {
|
||||
> State.LOADING -> show()
|
||||
> State.READY -> done()
|
||||
> } // error: 'when' expression must be exhaustive.
|
||||
> } // Add the 'ERROR' branch or an 'else' branch.
|
||||
> ```
|
||||
> А вот для `Int`, `String` и прочих открытых типов полноту проверить невозможно - там `when`-инструкция без `else` компилируется молча и просто ничего не делает:
|
||||
> ```kotlin
|
||||
> fun handle(code: Int) {
|
||||
> when (code) {
|
||||
> 200 -> show()
|
||||
> 404 -> fail()
|
||||
> } // для code = 500 тихо ничего не произойдёт
|
||||
> }
|
||||
> ```
|
||||
> Это ещё одна причина моделировать состояния через `enum` и `sealed`, а не через числа и строки: только тогда компилятор на вашей стороне.
|
||||
>
|
||||
> **Порядок ветвей важен.** Первая подошедшая выигрывает, более общая ветка выше «съест» частные:
|
||||
> ```kotlin
|
||||
> when (x) {
|
||||
> in 0..100 -> "диапазон" // сработает и для 42
|
||||
> 42 -> "ответ" // недостижимо
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> **`if` без `else` не выражение:**
|
||||
> ```kotlin
|
||||
> // val x = if (flag) 1 // ошибка компиляции
|
||||
> val x = if (flag) 1 else 0 // ok
|
||||
> ```
|
||||
>
|
||||
> **`is List<*>` не проверяет тип элементов** - дженерики стираются в рантайме. `is List<String>` написать нельзя. Подробнее: [Reified и inline](../../Дженерики/Reified%20и%20inline.md)
|
||||
|
||||
Далее: [Циклы и диапазоны](Циклы%20и%20диапазоны.md)
|
||||
@@ -0,0 +1,159 @@
|
||||
---
|
||||
title: Циклы и диапазоны
|
||||
description: for-in, диапазоны и прогрессии, while, метки и отсутствие классического for
|
||||
tags:
|
||||
- kotlin
|
||||
- syntax
|
||||
---
|
||||
|
||||
В Kotlin один цикл обхода - `for (x in source)`. Классического `for (int i = 0; i < n; i++)` нет: его роль выполняют диапазоны.
|
||||
|
||||
## Диапазоны
|
||||
```kotlin
|
||||
for (i in 1..5) print(i) // 12345 — включая 5
|
||||
for (i in 1..<5) print(i) // 1234 — исключая 5
|
||||
for (i in 5 downTo 1) print(i) // 54321
|
||||
for (i in 1..10 step 3) print(i) // 1 4 7 10
|
||||
for (c in 'a'..'e') print(c) // abcde
|
||||
```
|
||||
|
||||
| Конструкция | Смысл |
|
||||
|---|---|
|
||||
| `a..b` | от `a` до `b` включительно |
|
||||
| `a..<b` | от `a` до `b`, не включая `b` |
|
||||
| `a until b` | то же, что `a..<b` (старая форма) |
|
||||
| `a downTo b` | по убыванию, включительно |
|
||||
| `step n` | с шагом `n` |
|
||||
|
||||
Шаг всегда положительный, даже в `downTo`: направление задаёт `downTo`, а не знак шага.
|
||||
|
||||
Диапазон - это объект, его можно хранить и проверять:
|
||||
```kotlin
|
||||
val valid = 1..100
|
||||
println(42 in valid) // true
|
||||
println(valid.first) // 1
|
||||
println(valid.last) // 100
|
||||
println((1..10 step 3).last) // 10
|
||||
```
|
||||
|
||||
## Обход по индексам
|
||||
```kotlin
|
||||
val list = listOf("a", "b", "c")
|
||||
|
||||
for (i in list.indices) print(i) // 012
|
||||
for (i in 0..<list.size) print(i) // 012 — то же самое
|
||||
|
||||
for (item in list) print(item) // abc — предпочтительно
|
||||
for ((i, item) in list.withIndex()) { // 0:a 1:b 2:c
|
||||
print("$i:$item ")
|
||||
}
|
||||
```
|
||||
`withIndex()` возвращает пары, которые тут же разбираются деструктуризацией ([Деструктуризация](../Работа%20с%20данными/Деструктуризация.md)).
|
||||
|
||||
## Обход коллекций и Map
|
||||
```kotlin
|
||||
val map = mapOf("a" to 1, "b" to 2)
|
||||
for ((key, value) in map) {
|
||||
println("$key=$value")
|
||||
}
|
||||
```
|
||||
`for` работает с любым типом, у которого есть функция `iterator()` - включая свои классы.
|
||||
|
||||
## while и do-while
|
||||
```kotlin
|
||||
var i = 0
|
||||
while (i < 3) {
|
||||
print(i) // 012
|
||||
i++
|
||||
}
|
||||
|
||||
do {
|
||||
val line = readLine()
|
||||
} while (line != null) // тело выполнится минимум один раз
|
||||
```
|
||||
Переменная, объявленная в `do`, видна в условии `while` - такого в Java нет.
|
||||
|
||||
## break и continue
|
||||
```kotlin
|
||||
for (i in 1..10) {
|
||||
if (i == 3) continue // пропустить итерацию
|
||||
if (i == 5) break // выйти из цикла
|
||||
print(i) // 124
|
||||
}
|
||||
```
|
||||
|
||||
## Метки
|
||||
Метка - идентификатор с `@`. Нужна, чтобы выйти из внешнего цикла:
|
||||
```kotlin
|
||||
outer@ for (i in 1..3) {
|
||||
for (j in 1..3) {
|
||||
if (j == 2) continue@outer // к следующей i
|
||||
if (i == 3) break@outer // выход из обоих циклов
|
||||
print("$i$j ") // 11 21
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Циклы - не выражения
|
||||
`for` и `while` возвращают `Unit`, присвоить их результат нельзя. Если нужен результат обхода - это операции над коллекциями:
|
||||
```kotlin
|
||||
val doubled = (1..5).map { it * 2 } // [2, 4, 6, 8, 10]
|
||||
val evens = (1..10).filter { it % 2 == 0 } // [2, 4, 6, 8, 10]
|
||||
val sum = (1..100).sum() // 5050
|
||||
```
|
||||
В идиоматичном Kotlin явные циклы встречаются редко. Подробнее: [Операции над коллекциями](../../Коллекции/Операции.md)
|
||||
|
||||
## repeat
|
||||
Когда индекс не нужен:
|
||||
```kotlin
|
||||
repeat(3) { println("повтор") }
|
||||
repeat(3) { i -> println("итерация $i") } // индекс доступен, с нуля
|
||||
```
|
||||
Это не оператор, а [inline-функция](../../Функциональный%20Kotlin/Inline-функции.md) стандартной библиотеки.
|
||||
|
||||
> [!info] Отличия от Java
|
||||
> | Java | Kotlin |
|
||||
> |---|---|
|
||||
> | `for (int i=0; i<n; i++)` | `for (i in 0..<n)` |
|
||||
> | `for (T x : collection)` | `for (x in collection)` |
|
||||
> | `label: for (...)` | `outer@ for (...)` |
|
||||
> | `break label;` | `break@outer` |
|
||||
> | три части в заголовке `for` | только `in` |
|
||||
> | нет диапазонов | `1..10`, `step`, `downTo` |
|
||||
>
|
||||
> Что потеряли: заголовок `for` с несколькими переменными и произвольным условием. Если нужна такая логика - пишется `while`.
|
||||
>
|
||||
> Сравнить: [Циклы в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Циклы.md)
|
||||
|
||||
> [!warning] Подводные камни
|
||||
> **`1..5` включает 5.** Самая частая ошибка при переходе с Java: индексы массива - это `0..<size`, а не `0..size`:
|
||||
> ```kotlin
|
||||
> val list = listOf("a", "b", "c")
|
||||
> // for (i in 0..list.size) println(list[i]) // IndexOutOfBounds на i=3
|
||||
> for (i in 0..<list.size) println(list[i]) // верно
|
||||
> for (i in list.indices) println(list[i]) // ещё лучше
|
||||
> ```
|
||||
>
|
||||
> **`downTo` со `step` требует положительного шага:**
|
||||
> ```kotlin
|
||||
> for (i in 5 downTo 1 step 2) print(i) // 531
|
||||
> // for (i in 5 downTo 1 step -2) // IllegalArgumentException в рантайме
|
||||
> ```
|
||||
>
|
||||
> **Пустой диапазон не ошибка, просто ноль итераций:**
|
||||
> ```kotlin
|
||||
> for (i in 5..1) print(i) // ничего не напечатает
|
||||
> ```
|
||||
> Молчаливый баг, если границы перепутаны. `(5..1).isEmpty()` - `true`.
|
||||
>
|
||||
> **Коллекцию нельзя изменять во время обхода:**
|
||||
> ```kotlin
|
||||
> val list = mutableListOf(1, 2, 3)
|
||||
> // for (x in list) if (x == 2) list.remove(x) // ConcurrentModificationException
|
||||
> list.removeAll { it == 2 } // правильный способ
|
||||
> ```
|
||||
>
|
||||
> **Диапазон `Long` и `Char` есть, а `Double` - нет.** `1.0..2.0` создаст `ClosedFloatingPointRange`: проверять `in` можно, но обойти циклом - нельзя (нет `iterator()`).
|
||||
|
||||
Дополнительно: [Операции над коллекциями](../../Коллекции/Операции.md)
|
||||
Далее: [Строковые шаблоны и вывод](Строковые%20шаблоны%20и%20вывод.md)
|
||||
Reference in New Issue
Block a user