Запечатанные классы Kotlin и hashcode/equals

Я вижу, что пишу тест, в котором я не могу утверждать, что два запечатанных класса с одним и тем же «подклассом» и одинаковым значением под капотом равны. Они различны.

fun main() {

    val a1 = MySealed.A("foo")
    val a2 = MySealed.A("foo")

    System.out.println(a1 == a2)
    
    val a3 = MySealedWithEqualsAndHashCodeOverriden.A("foo")
    val a4 = MySealedWithEqualsAndHashCodeOverriden.A("foo")
    
    System.out.println(a3 == a4)
    
}

sealed class MySealed(val value: String) {
    class A(value: String) : MySealed(value)
}

sealed class MySealedWithEqualsAndHashCodeOverriden(val value: String) {
    class A(value: String) : MySealedWithEqualsAndHashCodeOverriden(value) {
         override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false
            return true
        }

        override fun hashCode(): Int {
            return javaClass.hashCode()
        }
    }
}

Эта основная функция возвращает:

false
true

Я действительно не понимаю, почему такое поведение. Я думаю, это связано с природой закрытых классов, и я этого не понимаю?

заранее спасибо

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
42
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Классы Kotlin Sealed не переопределяют equals() реализацию по умолчанию из Object класса Java. Это означает, что объекты сравниваются с использованием их ссылки, поэтому a1 и a2 не равны.

Классы данных Kotlin, в свою очередь, переопределяют метод equals() на основе всех свойств, объявленных в основном конструкторе (подробнее о них читайте в https://kotlinlang.org/docs/data-classes.html).

Спасибо за быстрый ответ. Итак, как вы думаете, имеет ли смысл переопределять метод запечатанного класса equals или это неприятный запах использования неправильного инструмента?

cort 12.05.2022 00:12

Это прекрасно. Но рассмотрите возможность использования классов данных, если они соответствуют вашему варианту использования;)

João Dias 12.05.2022 02:35
Ответ принят как подходящий

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

class NormalClass(val value: String)

val a = NormalClass("foo")
val b = NormalClass("foo")
println(a == b)

> false

data classes предоставляют реализацию equals и hashCode по умолчанию, которая игнорирует ссылочное равенство и просто сравнивает тип объекта и значения свойств в конструкторе.

data class DataClass(val value: String)

val a = DataClass("foo")
val b = DataClass("foo")
println(a == b)

> true

sealed class на самом деле просто специальный тип, которому может принадлежать класс, который в основном используется для таких вещей, как определение всех возможных объектов, которые имеют этот тип. Это позволяет вам группировать разрозненные классы и объекты вместе и выполнять такие действия, как исчерпывающее сопоставление с образцом (например, предложение when, работающее с объектом MySealed, может сказать, когда вы проверили все возможные члены этого типа)

Ваш класс A является обычным классом, поэтому два его экземпляра по умолчанию не равны. Если бы вы сделали это object в MySealed, это был бы только один экземпляр. В этом смысле он может работать как enum class. Закрытый класс позволяет смешивать и сочетать эти разные типы с некоторыми преимуществами и недостатками.

Вы можете просто создать A класс данных внутри запечатанного класса, если хотите, чтобы они совпадали, если у них одинаковые value, но также были MySealed

Другие вопросы по теме