В чем разница между двойным и тройным равенством в Kotlin
Что проверяют
Понимаете ли вы разницу между структурным и ссылочным равенством и знаете ли, во что компилируется ==. Следом часто просят объяснить, почему два одинаковых Int? могут оказаться не равны по ===, или как это связано с equals() в data-классах.
Как отвечать
==: структурное равенство. Компилятор превращаетa == bвa?.equals(b) ?: (b === null), поэтому сравнение безопасно кnullи не бросает NPE. Это аналогequals()из Java, а не==из Java;===: ссылочное равенство, проверка того, что обе переменные указывают на один объект.!==его отрицание;- data class: генерирует
equals()иhashCode()по свойствам главного конструктора, поэтому два одинаковых объекта равны по==, но не по===. Свойства из тела класса в сравнение не входят.
Хороший пример с числами: у Int? значения упаковываются в Integer, а JVM кэширует такие объекты от -128 до 127. Поэтому для двух Int? со значением 100 === даёт true, а для 1000 уже false. На === для таких типов компилятор выдаёт предупреждение, а для value-классов ссылочное сравнение запрещено вовсе.
Отдельная тонкость с Double: если тип известен статически, == следует IEEE 754 (NaN != NaN, -0.0 == 0.0), а если значение пришло как Any, сравнение идёт через equals(), и результат меняется.
В Android это всплывает в DiffUtil.ItemCallback: areItemsTheSame сравнивает идентификаторы, areContentsTheSame содержимое через ==, и корректный equals() у модели напрямую влияет на то, какие элементы списка перерисуются.
Где спотыкаются
Переносят привычку из Java и считают, что == в Kotlin сравнивает ссылки. Отсюда лишние вызовы equals() и путаница на код-ревью.
Проверяют «одинаковость» строк или чисел через === и получают тест, который проходит на маленьких значениях и падает на больших из-за кэша упаковки.
Переопределяют equals() вручную и забывают про hashCode(). Объект перестаёт находиться в HashSet и HashMap, хотя по == он равен искомому.