Kotlin для новичков: 5 фатальных ошибок и как их избежать

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему Kotlin кажется лёгким, но подкидывает сюрпризы

Kotlin — современный, лаконичный и невероятно удобный язык. Многие новички приходят в него из Java или Python и думают: «Ну, тут же всё понятно!». И действительно, синтаксис Kotlin чистый, nullable типы защищают от NPE, а функциональные фичи вроде let, apply и run выглядят магически просто. Но именно эта кажущаяся простота часто становится ловушкой.

В этой статье мы разберём самые распространённые ошибки при изучении Kotlin, которые совершают новички. Вы узнаете, как их избежать, и получите готовые примеры кода. Если вы только начинаете свой путь в изучении Kotlin для начинающих, эта статья спасёт вас от многих часов дебага.

Ошибка №1: Игнорирование null safety или, наоборот, паранойя

Kotlin вшил null safety прямо в систему типов. Это благо, но новички часто впадают в две крайности. Первая — полное игнорирование и использование оператора !! (not-null assertion) на каждом шагу. Вторая — чрезмерное использование элвис-оператора ?: и цепочек из ?.let, которые превращают код в нечитаемую кашу.

Как выглядит ошибка

// Плохо: тотальный !! — убивает всю безопасность Kotlin
fun processUser(user: User?) {
    val name = user!!.name  // Если user == null — краш!
    println(name.uppercase())
}

// Плохо: слишком много вложенных let
fun getUserCity(user: User?): String? {
    return user?.let {
        it.address?.let { addr ->
            addr.city?.let { city ->
                city.name
            }
        }
    }
}

Как правильно

// Хорошо: используем безопасный вызов + элвис
fun processUserSafe(user: User?) {
    val name = user?.name ?: "Гость"
    println(name.uppercase())
}

// Отлично: расширения и scope-функции без зоопарка вложенности
fun getUserCityClean(user: User?): String? {
    return user?.address?.city?.name
}

Совет: Оператор !! используйте только если вы на 100% уверены, что значение не null (например, после проверки в if). В остальных случаях — ?. и ?:.

Это одна из главных проблем Kotlin новичков — они либо боятся null, либо не понимают, как с ним работать. Помните: Kotlin не запрещает null, он заставляет вас думать о нём.

Ошибка №2: Непонимание разницы между var, val и константами

Кажется, что val — это финальная переменная, как final в Java. Но нет. val — это read-only ссылка, а не неизменяемый объект. Новички часто путают val с константой времени компиляции (const val) и удивляются, почему их код работает не так.

Пример путаницы

// Новичок думает: "val = неизменяемо"
val list = mutableListOf(1, 2, 3)
list.add(4)  // Работает! Но новичок в шоке

// А вот это уже ошибка компиляции
val name = "Alice"
name = "Bob"  // Ошибка: val не может быть переприсвоено

Как отличить

// Константа времени компиляции — только для примитивов и String
const val PI = 3.14159

// Read-only ссылка — может быть вычислена в runtime
val currentTime = System.currentTimeMillis()

// Настоящая неизменяемость — используйте immutable коллекции
val immutableList = listOf(1, 2, 3)  // listOf, а не mutableListOf

Золотое правило: Используйте val везде, где возможно. Если переменная должна меняться — только тогда var. А const val — только для констант верхнего уровня или внутри object/companion object.

Ошибка №3: Злоупотребление companion object и object-синглтонами

Kotlin предлагает элегантный способ создания синглтонов через object. Но новички часто делают companion object для всего подряд, превращая его в мусорную корзину для статических методов. Это нарушает принципы SOLID и делает код сложным для тестирования.

Типичный антипаттерн

class UserService {
    companion object {
        fun validateEmail(email: String): Boolean { /* ... */ }
        fun formatName(name: String): String { /* ... */ }
        const val DEFAULT_AVATAR = "default.png"
        fun createInstance(): UserService = UserService()
    }
}

Что делать вместо этого

// Для утилитарных функций — extension функции или top-level
fun String.isValidEmail(): Boolean {
    return this.contains("@")
}

// Для констант — top-level const val
const val DEFAULT_AVATAR = "default.png"

// Для фабричных методов — отдельный object или функция
object UserServiceFactory {
    fun create(): UserService = UserService()
}

// Для DI — используйте Koin/Hilt, не синглтоны вручную

Совет: companion object — не место для всего. Используйте его только для членов, которые тесно связаны с классом (например, фабричные методы или константы, специфичные для класса).

Это частая ошибка Kotlin, которая перекочевала из Java-мышления. В Kotlin лучше использовать top-level функции и extension функции — они более гибкие.

Ошибка №4: Путаница между == и ===, а также игнорирование data class

В Java == сравнивает ссылки, а equals() — содержимое. В Kotlin == — это структурное равенство (вызывает equals()), а === — ссылочное. Новички, пришедшие из Java, часто используют == для сравнения строк и получают неожиданные результаты, если не переопределён equals. Но есть и обратная сторона: они забывают про data class, который автоматически генерирует equals, hashCode, toString и copy.

Ошибка и её исправление

// Новичок пишет обычный класс и мучается
class Person(val name: String, val age: Int)

fun main() {
    val p1 = Person("Alice", 25)
    val p2 = Person("Alice", 25)
    println(p1 == p2)  // false! equals не переопределён
}

// Профи использует data class
data class Person(val name: String, val age: Int)

fun main() {
    val p1 = Person("Alice", 25)
    val p2 = Person("Alice", 25)
    println(p1 == p2)  // true! equals сгенерирован автоматически
}

Когда использовать ===

Блоги

Рекомендаций по книгам