Due in part to the fact that I cannot create data classes without parameters in Kotlin, I use object
s for those cases, e.g.
sealed class Node {
object Leaf : Node()
data class Branch(val left:Node, val right:Node) : Node()
}
The issue is that sometimes, I end up with multiple instances of the Leaf
class. Obviously this should not generally happen, but it occurs when serializing and deserializing with some frameworks and with some test cases.
Now, you might argue that I should fix those cases, but it's hard to know where they might be, and not always possible or desirable to modify the deserialization semantics of frameworks.
As such, I want all instances of my object
s to act as the same value, much like a parameter-less data class
would (or a parameterless case class
in Scala).
My best solution so far is the following, included in every object
I create that might encounter this issue:
object SaneLeaf {
override fun hashCode() = javaClass.hashCode()
override fun equals(other: Any?) = other?.javaClass == javaClass
}
Obviously, this is verbose and error prone, since it doesn't seem possible to abstract away those implementations to an interface. A super-class would work in cases where the object doesn't need to extend another class, but that's often not the case.
Alternatively, I can create a data class with a Nothing
parameter. But that seems like even more of a hack.
How have others dealt with this issue? Are there plans to add hashCode
and equals
implementations to objects that follow the presumed semantics of those classes (all instances should be equal / same hashCode)?
I believe having multiple instances of an object
's underlying class is really an issue you should fix, but there's a simpler workaround that allows you to have the equality semantics you described.
You can define an abstract class that performs the equality logic and make the sealed
class inherit from it, like this:
abstract class SingletonEquality {
override fun equals(other: Any?): Boolean =
this::class.objectInstance != null && other?.javaClass == this.javaClass ||
super.equals(other)
override fun hashCode(): Int =
if (this::class.objectInstance != null)
javaClass.hashCode() else
super.hashCode()
}
And the usage:
sealed class Node : SingletonEquality() {
object Leaf : Node()
data class Branch(val left:Node, val right:Node) : Node()
}
Here, Leaf
will inherit the equals
implementation from SingletonEquality
and get compared just the way you want.