Основы
Переменные и типы
Предпочитайте val (неизменяемое) var (изменяемое). Kotlin выводит типы, но допускает явные аннотации. Используйте 'is' для проверок типа (smart-cast автоматически). Всё — объект, нет примитивов в синтаксисе Kotlin.
val name = "Alice" // immutable (preferred)
var age = 30 // mutable
val pi: Double = 3.14159
val isDev: Boolean = true
val nums: List<Int> = listOf(1, 2, 3)
println(name::class) // class kotlin.String
println(age is Int) // trueСтроковые шаблоны
$var для простых переменных, ${expr} для выражений. Строки в тройных кавычках сохраняют переносы — используйте trimIndent() для чистого форматирования. Строковые шаблоны делают построение строк лаконичным и читаемым без строк форматирования.
val name = "Alice"
val age = 30
println("Name: $name, Age: $age") // simple var
println("Length: ${name.length}") // expression
println("Upper: ${name.uppercase()}")
val multiline = """
Hello, $name
Age: $age
""".trimIndent()Null-безопасность
Null-безопасность Kotlin: ? помечает типы, допускающие null, ?. безопасный вызов (возвращает null если null), !! утверждает не-null (вызывает NPE если null), ?: elvis (по умолчанию если null). Это устраняет NullPointerException во время компиляции — ключевая функция Kotlin.
var name: String? = null // nullable type
println(name?.length) // null (safe call)
name = "Alice"
println(name!!.length) // 5 (non-null assertion)
val len: Int = name?.length ?: 0 // elvis operator
// Non-null types can never be null
val s: String = "hi" // not nullableПреобразование типов
Kotlin требует явного преобразования типов (нет неявного расширения, как в Java). Используйте toInt()/toDouble()/toString() для преобразования. toIntOrNull() возвращает null при неудаче — безопаснее toInt(), который вызывает исключение. Всегда обрабатывайте null из разбора.
val n: Int = 42
val d: Double = n.toDouble()
val s: String = n.toString()
val fromStr: Int? = "100".toIntOrNull()
val invalid: Int? = "abc".toIntOrNull() // null
println(d, s, fromStr, invalid)Проверки типа и smart-cast
is проверяет тип и smart-cast переменную в этой ветке — без явного приведения. !is — отрицание. Используйте as для небезопасного приведения (вызывает ClassCastException), as? для безопасного (возвращает null при неудаче). Smart cast — крупное удобство Kotlin.
fun describe(x: Any): String =
when (x) {
is Int -> "Int: ${x + 1}" // x smart-cast to Int
is String -> "String of length ${x.length}"
is List<*> -> "List with ${x.size} items"
else -> "Unknown"
}
println(describe(42)) // Int: 43
println(describe("hello")) // String of length 5Строки
Распространённые строковые методы
Строки Kotlin имеют богатые функции расширения из стандартной библиотеки. Большинство возвращают новые строки (неизменяемые). Используйте их вместо ручных циклов. Функции вроде uppercase() учитывают локаль — используйте uppercase(Locale.ROOT) для согласованности.
val s = "Hello, World"
println(s.length) // 12
println(s.uppercase()) // HELLO, WORLD
println(s.lowercase()) // hello, world
println(s.split(", ")) // [Hello, World]
println(s.replace("o", "0")) // Hell0, W0rld
println(s.reversed()) // dlroW ,olleH
println(s.startsWith("Hello")) // trueМногострочные и сырые строки
Строки в тройных кавычках сырые — не нужны escape-последовательности (кроме тройных двойных кавычек). trimMargin('|') сохраняет только текст после | для чистого отступа. trimIndent() удаляет общий пробел. Идеально для SQL, JSON, HTML в коде.
val text = """
|Hello,
|World!
""".trimMargin() // | marks line start
println(text)
val json = """{"name": "Alice", "age": 30}"""
println(json) // no escape needed for quotes
// trimIndent() removes common leading whitespaceПостроение строк
joinToString — идиоматичный способ соединения коллекций с разделителями, префиксом, суффиксом. buildString предоставляет область StringBuilder для лаконичного построения. Избегайте повторного + в циклах — используйте joinToString или StringBuilder для эффективности.
val parts = listOf("apple", "banana", "cherry")
println(parts.joinToString(", ")) // apple, banana, cherry
println(parts.joinToString(prefix="[", postfix="]"))
val sb = StringBuilder()
for (p in parts) sb.append(p).append(" ")
println(sb.toString().trim())
// buildString for concise building
val result = buildString {
parts.forEach { append(it).append(";") }
}Строка в число
toInt()/toDouble() вызывают NumberFormatException при некорректном вводе. toIntOrNull() возвращает null — парируйте с ?: для безопасных значений по умолчанию. toBooleanStrict() принимает только 'true'/'false'. Используйте OrNull-варианты для пользовательского ввода или ненадёжных данных.
val n = "42".toInt() // 42 (throws if invalid)
val d = "3.14".toDouble() // 3.14
val safe = "abc".toIntOrNull() // null
val withDefault = "x".toIntOrNull() ?: 0 // 0
val bool = "true".toBooleanStrict() // true
println(n, d, safe, withDefault)Регулярные выражения
Класс Regex оборачивает Pattern Java. find возвращает MatchResult? (первое совпадение), findAll возвращает все. matches проверяет полное совпадение, containsMatchIn — частичное. Используйте строки в тройных кавычках для шаблонов во избежание двойного экранирования обратных слэшей.
val email = Regex("[\w.]+@[\w]+\.[a-z]+")
val text = "Contact: [email protected]"
val match = email.find(text)
println(match?.value) // [email protected]
println(email.matches("[email protected]")) // true
val replaced = text.replace(Regex("\d+"), "#")
println(email.containsMatchIn(text)) // trueСтруктуры данных
Список
listOf создаёт неизменяемые списки, mutableListOf для изменяемых. Списки индексируются с нуля. Используйте first()/last() для концов (вызывают исключение при пустом), firstOrNull() для безопасности. contains проверяет принадлежность. Предпочитайте неизменяемые списки, если не нужно изменять.
val nums = listOf(1, 2, 3) // immutable
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)
mutable[0] = 0
println(nums.size) // 3
println(nums.first()) // 1
println(nums.last()) // 3
println(nums.contains(2)) // true
println(nums.indexOf(2)) // 1Map
mapOf создаёт неизменяемые map, mutableMapOf для изменяемых. Инфикс 'to' создаёт пары. Доступ [] возвращает nullable V? (null если отсутствует). Используйте getOrDefault или getValue (вызывает исключение, если отсутствует). Итерируйте с деструктуризацией (k, v).
val ages = mapOf("Alice" to 30, "Bob" to 25)
println(ages["Alice"]) // 30
println(ages.getOrDefault("Eve", 0)) // 0
println(ages.containsKey("Alice")) // true
for ((k, v) in ages) println("$k: $v")
val mutable = mutableMapOf("a" to 1)
mutable["b"] = 2 // add/updateМножество
setOf создаёт неизменяемые множества, mutableSetOf для изменяемых. union/intersect/subtract возвращают новые множества. Множества обеспечивают уникальность — add возвращает false, если элемент существует. Используйте для дедупликации и операций над множествами. LinkedHashSet сохраняет порядок вставки.
val a = setOf(1, 2, 3)
val b = setOf(3, 4, 5)
println(a union b) // [1,2,3,4,5]
println(a intersect b) // [3]
println(a subtract b) // [1,2]
println(a.contains(2)) // true
val mutable = mutableSetOf(1, 2)
mutable.add(3) // true if addedМассив и примитивные массивы
arrayOf создаёт Array<T> (упакованный для примитивов). Используйте intArrayOf/doubleArrayOf для неупакованных примитивных массивов (лучшая производительность). Array(size) { init } создаёт с функцией инициализации. Массивы изменяемы; предпочитайте List для большинства случаев.
val arr = arrayOf(1, 2, 3) // Array<Int>
arr[0] = 10
println(arr.size) // 3
// Specialized arrays for primitives (no boxing)
val ints = intArrayOf(1, 2, 3)
val doubles = doubleArrayOf(1.0, 2.0)
// Array constructor
val squares = Array(5) { it * it } // [0,1,4,9,16]
println(squares.toList())Pair и Triple
Pair и Triple группируют 2-3 значения. Инфикс 'to' создаёт Pair (часто для map). Деструктурируйте через val (a, b) = pair. Для более 3 значений или именованных полей используйте data-классы — они яснее и поддерживаемее.
val pair = "Alice" to 30 // Pair<String, Int>
println(pair.first) // Alice
println(pair.second) // 30
val (name, age) = pair // destructuring
val triple = Triple(1, "a", 2.0)
println(triple.first, triple.third)
// Useful for returning two values
fun minMax(list: List<Int>): Pair<Int, Int> =
Pair(list.min(), list.max())Поток управления
If / Else (выражение)
В Kotlin if/else — выражение, возвращающее значение. Это заменяет тернарный оператор. Обе ветви должны возвращать совместимые типы. Используйте для лаконичного условного присваивания. Как оператор (без else), возвращает Unit.
val score = 85
val grade = if (score >= 90) "A"
else if (score >= 80) "B"
else if (score >= 70) "C"
else "F"
println(grade) // B
// if returns a value—no ternary needed
// Both branches must have compatible typesWhen (Switch)
when — мощный switch Kotlin — поддерживает литералы, диапазоны (in), отрицание (!in), несколько значений (запятая) и is-проверки. Должно быть исчерпывающим при использовании как выражение с sealed-типами. Без субъекта — более чистая if/else цепочка.
val n = 2
val label = when (n) {
0 -> "zero"
1, 2, 3 -> "small"
in 4..10 -> "medium"
!in 1..100 -> "out of range"
else -> "large"
}
println(label) // small
// When without subject = multi-condition if
when {
n > 0 -> println("positive")
n < 0 -> println("negative")
else -> println("zero")
}Циклы For и диапазоны
.. — включающий диапазон, until — исключающий, downTo считает вниз, step задаёт шаг. withIndex() даёт индексированную итерацию. for-in работает с любым Iterable. Эти диапазоны лаконичны и читаемы для счётных циклов.
for (i in 0 until 5) print(i) // 01234 (excludes 5)
for (i in 1..5) print(i) // 12345 (includes 5)
for (i in 5 downTo 1) print(i) // 54321
for (i in 1..10 step 2) print(i) // 13579
val list = listOf("a", "b", "c")
for ((index, value) in list.withIndex()) {
println("$index: $value")
}While и Do-While
while проверяет до, do-while проверяет после (выполняется хотя бы один раз). Оба требуют изменяемого состояния. Предпочитайте циклы for или функциональные операции (map, filter) для неизменяемости. Используйте while для циклов, управляемых условием, где счётчик неизвестен.
var count = 0
while (count < 3) {
println(count)
count++
}
var j = 0
do {
println(j)
j++
} while (j < 3)
// do-while runs at least once
// Prefer for loops when possibleBreak и Continue (метки)
continue пропускает к следующей итерации, break выходит из цикла. Метки (@outer) обеспечивают выход из вложенных циклов — определите через label@ перед циклом, break/continue с @label. Используйте умеренно; рефакторинг в функции часто чище.
for (i in 1..5) {
if (i == 3) continue // skip 3
if (i == 5) break // stop at 5
println(i) // 1, 2, 4
}
// Labeled breaks for nested loops
outer@ for (i in 1..3) {
for (j in 1..3) {
if (i == 2 && j == 2) break@outer
println("$i,$j")
}
}Функции и лямбды
Определение функции
Функции используют fun name(params): ReturnType. Тело-выражение (= expr) для одиночных выражений. Поддерживаются параметры по умолчанию и именованные аргументы. Возврат Unit = только побочный эффект. Параметры по умолчанию уменьшают необходимость перегрузки.
fun add(a: Int, b: Int): Int = a + b // expression body
fun greet(name: String, greeting: String = "Hello"): String {
return "$greeting, $name!"
}
println(add(3, 4)) // 7
println(greet("Alice")) // Hello, Alice!
println(greet("Bob", greeting = "Hi")) // named arg
fun log(msg: String): Unit = println(msg) // Unit = voidЛямбды и функции высшего порядка
Лямбды: { params -> body }. 'it' — сокращение для одного параметра. Передавайте лямбды в функции высшего порядка вроде map/filter/reduce. Синтаксис завершающей лямбды: если последний параметр — функция, она может идти за (). Это идиоматичный Kotlin.
val square: (Int) -> Int = { x -> x * x }
println(square(5)) // 25
val nums = listOf(1, 2, 3)
println(nums.map { it * 2 }) // [2, 4, 6]
println(nums.filter { it > 1 }) // [2, 3]
println(nums.reduce { a, b -> a + b }) // 6
// 'it' is the implicit single parameterФункции расширения
Функции расширения добавляют методы к существующим классам (даже из Java) без наследования или модификации. Это синтаксический сахар для статических функций. 'this' ссылается на получателя. Используйте для fluent API. Разрешаются во время компиляции (без динамической диспетчеризации).
fun String.shout(): String = this.uppercase() + "!"
fun Int.isEven(): Boolean = this % 2 == 0
println("hello".shout()) // HELLO!
println(4.isEven()) // true
// Extensions don't modify the class—they're resolved statically
// Use to add utility methods to any typeInline и Infix
inline копирует тело функции в места вызова (устраняет накладные расходы лямбды). Используйте для функций высшего порядка в горячих путях. infix включает вызовы функций без точки/скобок (a op b) — используйте для DSL и читаемых операций вроде 'to', 'in', 'and'.
inline fun measure(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println("Took ${System.currentTimeMillis() - start}ms")
}
measure { println("working...") }
// Infix functions for readable calls
infix fun Int.times(str: String): String = str.repeat(this)
println(3 times "ab") // abababVararg и Spread
vararg принимает переменные аргументы (собираются в массив). * разворачивает массив в vararg. Именованные параметры могут следовать за vararg. Используйте для гибких API вроде listOf(), printf-подобных функций. Оператор spread — эквивалент spread JS в Kotlin.
fun sum(vararg nums: Int): Int = nums.sum()
println(sum(1, 2, 3, 4)) // 10
// Spread operator to pass array
val arr = intArrayOf(1, 2, 3)
println(sum(*arr)) // 6
// Named/default params can follow vararg
fun greet(vararg names: String, greeting: String = "Hi") =
names.joinToString { "$greeting, $it!" }
println(greet("A", "B"))Классы и ООП
Класс и конструктор
Первичный конструктор в заголовке класса. Параметры val/var становятся свойствами (с getter/setter). Без val/var они просто параметры конструктора (недоступны). Блок init выполняется при конструировании. Kotlin генерирует getter/setter автоматически.
class Person(val name: String, val age: Int) {
fun greet(): String = "Hi, I'm $name"
fun isAdult(): Boolean = age >= 18
}
val p = Person("Alice", 30)
println(p.greet()) // Hi, I'm Alice
println(p.name) // Alice (val = property)
println(p.isAdult()) // true
// val/var in constructor = auto propertyData-класс
data class автогенерирует equals, hashCode, toString, copy и componentN (для деструктуризации). Используйте для держателей данных. Должен иметь хотя бы один val/var параметр. copy() создаёт модифицированный клон — отлично для неизменяемых обновлений. Деструктурируйте через val (a, b) = point.
data class Point(val x: Int, val y: Int)
val p1 = Point(3, 4)
val p2 = Point(3, 4)
println(p1 == p2) // true (value equality)
println(p1.copy(x = 5)) // Point(x=5, y=4)
println(p1) // Point(x=3, y=4)
// Auto: equals, hashCode, toString, copy, componentN
val (x, y) = p1 // destructuring via componentN()Sealed-класс
Sealed-классы ограничивают подтипы тем же файлом/модулем kotlin. Выражения when исчерпывающие (компилятор проверяет все случаи). Используйте для ADT (алгебраических типов данных), представляющих конечные состояния. Сочетая с when, обеспечивает безопасное, проверяемое на этапе компиляции сопоставление шаблонов.
sealed class Result
data class Success(val value: Int) : Result()
data class Failure(val error: String) : Result()
object Loading : Result()
fun handle(r: Result): String = when (r) {
is Success -> "Got ${r.value}"
is Failure -> "Error: ${r.error}"
Loading -> "Loading..."
}
// when is exhaustive—compiler enforces all casesObject и Companion
object объявляет singleton (один экземпляр, лениво инициализируется). companion object внутри класса содержит 'static' члены (доступ через ClassName.member). Используйте object для синглтонов, companion для фабричных методов и констант. Companion может реализовывать интерфейсы.
object Config { // singleton
val version = "1.0"
fun load() = mapOf("key" to "value")
}
println(Config.version) // 1.0
class User(val name: String) {
companion object {
fun create(name: String) = User(name)
const val MAX = 100
}
}
val u = User.create("Alice") // like static methodНаследование и интерфейс
Классы по умолчанию final — используйте 'open' для разрешения наследования. 'override' обязателен. Интерфейсы могут иметь реализации по умолчанию. Класс расширяет один класс, но реализует несколько интерфейсов. Используйте abstract для частичной реализации. Предпочитайте композицию наследованию.
open class Animal(val name: String) {
open fun speak() = "..."
}
class Dog(name: String) : Animal(name) {
override fun speak() = "Woof"
}
interface Greetable {
val name: String
fun greet(): String // can have default impl
}
class Person(override val name: String) : Greetable {
override fun greet() = "Hi, $name"
}Null-безопасность и scope-функции
Безопасные вызовы и Elvis
?. безопасный вызов (возвращает null, если получатель null), ?: elvis (предоставляет значение по умолчанию). Цепочка ?. для глубокого доступа (user?.address?.city). Используйте ?: return/throw для ранних выходов. Это делает обработку null лаконичной и безопасной — без явных проверок null.
var name: String? = "Alice"
println(name?.length) // 5
name = null
println(name?.length) // null
println(name?.length ?: 0) // 0 (elvis: default if null)
println(name?.length ?: return) // early return
// Safe call chain
val city: String? = user?.address?.citylet (проверка null)
let выполняет блок только если значение не null. 'it' — не-null значение внутри блока. Используйте для null-защищённых операций вместо if-null проверок. Распространённый паттерн: value?.let { ... } для безопасной обработки. Возвращает результат блока.
var name: String? = "Alice"
name?.let {
println("Name is $it") // only runs if not null
println("Length: ${it.length}") // it is non-null
}
// Common for null-guarded blocks
val result = name?.let {
process(it) // it: String (non-null)
} ?: "default"apply и also
apply настраивает объект (возвращает объект, 'this' получатель) — идеально для построителей. also выполняет побочные эффекты (возвращает объект, 'it' параметр) — хорошо для логирования/отладки в цепочках. Оба возвращают исходный объект, обеспечивая fluent-цепочки.
val list = mutableListOf<Int>().apply {
add(1); add(2); add(3) // 'this' = the list
println("Built $size items")
}
val config = Config().also {
it.timeout = 30 // 'it' = config
println("Configured")
}
// apply: configure and return object (this)
// also: side effects, return object (it)run и with
run выполняет блок на объекте (возвращает результат блока, 'this' получатель) — используйте для преобразования объекта. with похож на run, но принимает объект как параметр (не цепляется на nullable). Используйте для группировки операций над одним объектом.
val result = "Hello".run {
length // 'this' = string, returns last expr
} // 5
val r2 = with(StringBuilder()) {
append("a"); append("b")
toString() // returns this
}
// run: object.run { } - returns block result
// with: with(obj) { } - returns block result (not chainable)TakeIf и TakeUnless
takeIf возвращает объект, если предикат истинен, иначе null. takeUnless — наоборот. Используйте для условной фильтрации в цепочках — избегает отдельных if-проверок. Комбинируйте с ?: для значений по умолчанию. Элегантно для конвейеров валидации.
val age = 25
val valid = age.takeIf { it >= 18 } // 25 (if true)
val invalid = age.takeIf { it < 18 } // null (if false)
val adult = age.takeUnless { it < 18 } // 25
// Useful for filtering in chains
val email = getUser()?.email?.takeIf { it.contains("@") }
println(valid, invalid, adult)Коллекции и функциональное
Map / Filter / Fold
map преобразует, filter выбирает, reduce/fold агрегирует. 'it' — неявный элемент. fold принимает seed; reduce не делает (вызывает исключение при пустом). Это ядро функциональной обработки коллекций — используйте вместо циклов для ясности.