Kotlin компилируется в байткод JVM (а ещё в JS, Wasm и нативный код), полностью совместим с Java и с 2019 года считается основным языком Android. На собесе по основам проверяют не синтаксис, а понимание: что такое выражение, как работает равенство, чем val отличается от неизменяемости.
val и var
val name = "Kotlin" // ссылку нельзя переназначить
var count = 0 // можно
count += 1
val запрещает переприсваивание, но не делает объект неизменяемым: val list = mutableListOf(1) позволяет list.add(2). Правило хорошего тона: по умолчанию val, var только когда действительно нужно.
const val это константа времени компиляции: только примитивы и String, только на верхнем уровне, в object или companion object. Значение подставляется в место использования.
Типы
Тип выводится сам, но он статический: val x = 1 навсегда Int. Числа: Byte, Short, Int, Long, Float, Double. Неявных расширяющих преобразований нет:
val i: Int = 1
val l: Long = i // ошибка компиляции
val l2: Long = i.toLong() // так можно
Под капотом Int компилируется в примитив int, а Int? или Int внутри List<Int> в объект Integer (boxing). Есть беззнаковые UInt, ULong. Any это корень иерархии (как Object), Unit это «ничего полезного» (как void, но настоящий объект), Nothing это тип выражения, которое никогда не завершается нормально (throw, TODO()).
Всё, что можно, это выражение
if, when и try возвращают значение, поэтому тернарного оператора нет:
val max = if (a > b) a else b
val label = when (code) {
200, 204 -> "ok"
in 400..499 -> "client error"
else -> "other"
}
val n = try { s.toInt() } catch (e: NumberFormatException) { 0 }
when без аргумента заменяет цепочку if-else. Если when используется как выражение, ветка else обязательна, кроме случаев, когда компилятор видит все варианты (enum, sealed, Boolean).
Строки и шаблоны
val user = "Аня"
println("Привет, $user! Длина: ${user.length}")
val json = """
{"name": "$user"}
""".trimIndent()
Сырые строки в тройных кавычках не обрабатывают \n и удобны для регулярок и JSON.
Диапазоны и циклы
for (i in 1..5) {} // 1, 2, 3, 4, 5
for (i in 0 until n) {} // 0..n-1, то же что 0..<n
for (i in 10 downTo 0 step 2) {}
for ((index, value) in list.withIndex()) {}
Классического for (int i = 0; ...) нет. repeat(3) { } выполняет блок три раза.
Равенство
==это структурное равенство, вызываетequalsи безопасно кnull;===это ссылочное равенство (тот же объект).
val a = "hi"
val b = String("hi".toCharArray())
println(a == b) // true
println(a === b) // false
Частая ловушка с упакованными числами: val x: Int? = 1000; val y: Int? = 1000; x === y может быть false, потому что это разные объекты Integer (кеш JVM покрывает только -128..127).
Функции верхнего уровня
В Kotlin функции и свойства не обязаны жить в классе. Файл Utils.kt с функцией fun hello() компилируется в класс UtilsKt со статическим методом. Точка входа fun main() тоже функция верхнего уровня.
Частые ошибки
- Считать, что
valделает коллекцию неизменяемой. - Ждать неявного
Int→Long, как в Java. - Путать
==и===. - Писать
ifсreturnв каждой ветке вместо выражения. - Использовать
1..nв цикле по индексам и выходить за границу: нуженuntilилиindices.
Что спросят на собесе
- Чем
valотличается отconst valи от неизменяемого объекта? - Чем
==отличается от===? - Что такое
Unit,NothingиAny? - Почему в Kotlin нет тернарного оператора?
- Когда
Intпревращается в объект на JVM?