Первые часы с Kotlin — сплошные «а, так можно было»: меньше точек с запятой, меньше типов, меньше повторов. Но за мелочами стоят два системных сдвига: почти всё в Kotlin — выражение, а не инструкция, и неизменяемость — выбор по умолчанию. Разберём базу, которой хватит, чтобы читать чужой код.
val и var: неизменяемость по умолчанию
val name = "Order-42" // неизменяемая ссылка, аналог final
var retries = 0 // изменяемая
retries++ // ок
// name = "Other" // не компилируется
Тип выводится из значения — писать String name не нужно, но можно: val name: String = "Order-42". Правило стиля простое: везде val, var — только когда без него не обойтись. В Java final у локальных переменных пишут редко, потому что многословно; в Kotlin неизменяемость короче изменяемости — и оттого встречается повсеместно.
Функции: от инструкций к выражениям
fun grade(percent: Int): String {
return if (percent >= 90) "отлично" else "нормально"
}
fun grade2(percent: Int) = if (percent >= 90) "отлично" else "нормально"
Здесь два новшества сразу. Первое: if — выражение, оно возвращает значение, поэтому тернарный оператор в Kotlin не нужен — его роль играет обычный if/else. Второе: функцию из одного выражения можно записать через = без фигурных скобок и return.
Функции живут и вне классов — на верхнем уровне файла. Утилитный класс со статическими методами превращается в файл с функциями.
when: switch, который дорос
val label = when (status) {
"NEW", "PENDING" -> "в работе"
"DONE" -> "готово"
else -> "неизвестно"
}
when — тоже выражение. Ветки не «проваливаются» (никакого break), можно матчить по диапазонам (in 1..9), типам (is String) и произвольным условиям (when { x > 0 -> ... }). В связке с sealed-классами компилятор проверяет полноту веток — об этом в статье про классы.
Шаблоны строк
val msg = "Заказ $orderId: ${items.size} позиций"
Вместо конкатенации или String.format — подстановка прямо в строке: $имя для переменной, ${выражение} для выражения. Многострочные строки — в тройных кавычках """...""", без экранирования.
Именованные аргументы и значения по умолчанию
fun connect(host: String, port: Int = 5432, ssl: Boolean = false) { /* ... */ }
connect("db.local") // порт и ssl — по умолчанию
connect("db.local", ssl = true) // именованный аргумент
Это закрывает две больные темы Java разом: телескопические перегрузки (пять версий метода с разным числом параметров) и билдеры ради читаемости вызова. Один метод со значениями по умолчанию заменяет всю лестницу перегрузок.
Мелочи, которые встретятся сразу
- Точка с запятой не нужна (можно, но линтер осудит).
newнет:val user = User("Anna").- Приведение типов — через
as, проверка — черезis; после успешной проверкиisкомпилятор сам считает тип уточнённым (smart cast) — явный каст не нужен. - Пакеты и импорты — как в Java; имя файла не обязано совпадать с именем класса.
Unit— аналогvoid, но полноценный тип; в сигнатурах его обычно опускают.
Коротко
val— неизменяемая ссылка,var— изменяемая; правило: везде val, var — по необходимости.ifиwhen— выражения: возвращают значение, тернарный оператор не нужен, ветки when не проваливаются.- Функции из одного выражения пишутся через
=; функции могут жить вне классов. - Шаблоны строк
$xи${expr}заменяют конкатенацию; именованные аргументы и значения по умолчанию — телескопические перегрузки и билдеры. - Smart cast: после
is-проверки тип уточняется автоматически.
Что почитать дальше
- Null-safety: типы против NPE — главное отличие системы типов.
- Классы: data, sealed, object — куда делись геттеры и сеттеры.
- Функции и коллекции — лямбды и extension-функции.