Равенство

В Kotlin есть два типа равенства:

  • Равенство структур (==) - проверка функции equals()
  • Равенство ссылок (===) - проверка того, что две ссылки указывают на один и тот же объект

Равенство структур

Равенство структур проверяет, имеют ли два объекта одинаковое содержимое или структуру. Оно проверяется оператором == и его отрицанием !=. По соглашению выражение a == b транслируется в:

a?.equals(b) ?: (b === null)

Если a не null, вызывается функция equals(Any?). Иначе (a равно null) проверяется, что b ссылочно равно null:

fun main() {
    var a = "hello"
    var b = "hello"
    var c = null
    var d = null
    var e = d

    println(a == b)
    // true
    println(a == c)
    // false
    println(c == e)
    // true
}

{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}

Заметьте, что в явном сравнении с null для оптимизации нет смысла: a == null будет автоматически транслироваться в a === null.

В Kotlin функция equals() наследуется всеми классами от класса Any. По умолчанию функция equals() реализует равенство ссылок. Однако классы в Kotlin могут переопределять функцию equals(), чтобы предоставить собственную логику равенства и таким образом реализовать равенство структур.

Классы-значения (value classes) и data-классы - два специальных типа Kotlin, которые автоматически переопределяют функцию equals(). Поэтому они реализуют равенство структур по умолчанию.

Однако для data-классов, если функция equals() помечена как final в родительском классе, ее поведение остается неизменным.

В отличие от них, обычные классы (не объявленные с модификатором data) по умолчанию не переопределяют функцию equals(). Вместо этого такие классы реализуют поведение равенства ссылок, унаследованное от класса Any. Чтобы реализовать равенство структур, таким классам нужна собственная логика равенства, переопределяющая функцию equals().

Чтобы предоставить собственную реализацию проверки равенства, переопределите функцию equals(other: Any?): Boolean:

class Point(val x: Int, val y: Int) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Point) return false

        // Сравнивает свойства для структурного равенства
        return this.x == other.x && this.y == other.y
    }
}

При переопределении функции equals() также следует переопределить функцию hashCode(), чтобы сохранить согласованность между равенством и хешированием и обеспечить правильное поведение этих функций. {:.note}

Функции с тем же именем и другими сигнатурами, например equals(other: Foo), не влияют на проверку равенства с помощью операторов == и !=.

Структурное равенство не имеет ничего общего со сравнением, определяемым интерфейсом Comparable<...>, поэтому только пользовательская реализация equals(Any?) может повлиять на поведение оператора.

Равенство ссылок

Равенство ссылок проверяет адреса памяти двух объектов, чтобы определить, являются ли они одним и тем же экземпляром.

Равенство ссылок проверяется с помощью оператора === и его отрицания !==. Выражение a === b является истиной тогда и только тогда, когда a и b указывают на один и тот же объект:

fun main() {
    var a = "Hello"
    var b = a
    var c = "world"
    var d = "world"

    println(a === b)
    // true
    println(a === c)
    // false
    println(c === d)
    // true

}

{kotlin-runnable=“true” kotlin-min-compiler-version=“1.3”}

Для значений, представленных примитивными типами во время выполнения (например, Int), проверка равенства === эквивалентна проверке ==.

В Kotlin/JS равенство ссылок реализовано иначе. Подробнее о равенстве см. в документации Kotlin/JS. {:.note}

Равенство чисел с плавающей точкой

Когда статически известно, что операнды проверки равенства являются Float или Double (nullable или нет), проверка выполняется в соответствии со стандартом IEEE 754 для арифметики с плавающей точкой.

Для операндов, которые статически не типизированы как числа с плавающей точкой, поведение отличается. В таких случаях реализуется равенство структур. В результате проверки с операндами, которые статически не типизированы как числа с плавающей точкой, отличаются от стандарта IEEE. В этом случае:

  • NaN равно самому себе
  • NaN больше любого другого элемента (включая POSITIVE_INFINITY)
  • -0.0 не равно 0.0

Подробнее см. в разделе Сравнение чисел с плавающей точкой.

Равенство массивов

Чтобы проверить, содержат ли два массива одинаковые элементы в одном и том же порядке, используйте contentEquals().

Подробнее см. в разделе Сравнение массивов.