Kotlin sealed classes and hashcode/equals

3.1k views Asked by At

I'm seeing writing a test that I cannot assert two sealed classes with same "subclass" and same value under the hood are equal. They are distinct.

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()
        }
    }
}

This main function returns:

false
true

I do not really get why that behaviour.. I guess it is related to the nature of sealed classes it self and I'm not getting it?

Thanks in advance

2

There are 2 answers

0
cactustictacs On BEST ANSWER

That's normal behaviour for any class - two different instances are not equal by default, because it checks for referential equality (i.e. the two references are pointing at the same object in memory).

class NormalClass(val value: String)

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

> false

data classes provide a default equals and hashCode implementation that ignores referential equality, and just compares the object type, and the values of the properties in the constructor

data class DataClass(val value: String)

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

> true

A sealed class is really just a special type that a class can belong to, which is mostly used for things like defining all the possible objects that have that type. It allows you to group disparate classes and objects together, and do things like exhaustive pattern matching (e.g. a when clause operating on a MySealed object can tell when you've checked all the possible members of that type)

Your A class is a normal class, so two instances of it are not equal by default. If you made it an object in MySealed, there would only be one instance of it. In that sense, it can operate a little like an enum class. A sealed class lets you mix and match these different types, with some benefits and drawbacks

You can just make A a data class inside the sealed class, if you want them to match if they have the same value, but also be a MySealed

2
João Dias On

Kotlin Sealed classes do not override the default equals() implementation from the Object Java class. This means that the objects are compared using their reference, hence a1 and a2 are not equal.

Kotlin Data classes in their turn do override the equals() method based on all properties declared in the primary constructor (read more about them at https://kotlinlang.org/docs/data-classes.html).