KotlinJuniorTechnical
В чём разница между == и === в Kotlin?
== вызывает equals() (структурное сравнение), === сравнивает ссылки (идентичность объектов). Для data class == сравнивает поля; для обычного класса без переопределения equals — эквивалентно ===.
== — структурное равенство
Оператор == в Kotlin транслируется в вызов a?.equals(b) ?: (b === null). То есть это безопасный вызов equals() с null-обработкой. Для data class компилятор генерирует equals(), сравнивающий все свойства из primary constructor.
data class Point(val x: Int, val y: Int)
val p1 = Point(1, 2)
val p2 = Point(1, 2)
val p3 = p1
println(p1 == p2) // true — equals() по полям
println(p1 === p2) // false — разные объекты в памяти
println(p1 === p3) // true — одна и та же ссылка
println(p1 == p3) // true — equals() тоже true
=== — референциальное равенство
Оператор === проверяет, что два операнда указывают на один и тот же объект в памяти (аналог == в Java для объектов). Для примитивных типов (Int, Long, …) при значениях в диапазоне JVM-кэша результат может быть неожиданным.
// Обычный класс — equals не переопределён
class Box(val value: Int)
val b1 = Box(5)
val b2 = Box(5)
println(b1 == b2) // false — equals по умолчанию == ===
println(b1 === b2) // false
// Null-безопасность ==
val s: String? = null
println(s == null) // true — безопасно
// s.equals(null) // NullPointerException в Java, в Kotlin — ошибка компиляции без ?
Числовые типы и boxing
val a: Int = 1000
val b: Int = 1000
println(a === b) // true для примитивных Int (unboxed)
val x: Int? = 1000
val y: Int? = 1000
println(x === y) // false! nullable Int — boxed объекты на куче
Строки
val s1 = "hello"
val s2 = "hello"
println(s1 == s2) // true — equals
println(s1 === s2) // true — JVM intern pool: литералы интернируются
val s3 = String(charArrayOf('h','e','l','l','o'))
println(s1 == s3) // true — equals
println(s1 === s3) // false — новый объект
Подводные камни
- Для
Int?и других nullable-примитивов===сравнивает boxed-объекты — результат зависит от JVM-кэша (-128..127 для Integer). - Если не переопределить
equals()в обычном классе,==работает как===— это частая ошибка при сравнении объектов. data classучитывает только свойства из primary constructor — свойства в теле класса игнорируются вequals/hashCode.- При использовании в
SetилиMapнарушение контрактаequals/hashCodeприводит к непредсказуемому поведению. - Kotlin/JS:
===транслируется в===JavaScript, где семантика отличается от JVM. compareByиComparatorиспользуютcompareTo, а неequals— важно не путать равенство и порядок.
Common mistakes
- Объяснять «== и ===» только как синтаксис и не описывать поведение runtime/compiler.
- Игнорировать важный риск: Сравнение доменных объектов через === почти всегда ломает бизнес-логику, если нужен value comparison.
- Давать пример без edge case: отмены, null, recomposition, platform boundary или ошибки.
What the interviewer is testing
- Формулирует суть темы «== и ===» своими словами и связывает ее с кодом.
- Называет механизм: Для nullable значений == безопасно раскладывается в equals с null-check, поэтому выражение не падает на null.
- Видит production-последствие: Сравнение доменных объектов через === почти всегда ломает бизнес-логику, если нужен value comparison.