Stepping back a bit:
kotlin.Any
(which all Kotlin objects ultimately extend) has methods equals()
and hashCode()
. So you can call those methods on every object.
That’s why ==
can use equals()
; it will always work.
Any
has a very basic, default implementation of equals()
: it uses reference equality, which treats an object as equal to itself but different from every other object (even one with the same class and fields).
That’s the safest implementation, and in many cases it’s just what you want. (For example, if you make two Socket
connections to the same port, you’ll still need to treat them differently.)
When you do want some different objects to be treated as equal (e.g. in value objects), then you’ll need to override equals()
(and hashCode()
) to specify how to compare the objects.
Writing an equals()
implementation isn’t easy; the docs for theat method spell out several conditions that an implementation must follow, and it’s easy to break them by mistake. (Which leads to some very subtle and hard-to-find bugs.)
I’d suggest reading this article, which spells out some of the common pitfalls, and how to avoid them. (It even gives a pattern for handling equality between superclasses and subclasses, but that’s a pretty advanced and complex approach, and it’s usually much simpler and safer not to allow that.)
And if you do override equals()
, then you must override hashCode()
too, so that they match.
That’s why Kotlin provides data class
es, which take care of all those things for you! (And much more, besides.) If you’re writing a value class, I’d strongly consider trying to make it a data class
, to take advantage of all that. (Or if not, consider whether you could simply fall back to Any
’s reference equality.)
But as a basic idea, here’s the approach I generally use:
override fun equals(other: Any?) = other === this ||
(other is ThisClass
&& field1 == other.field1
&& field2 == other.field2
&& field3 == other.field3)
override fun hashCode() = (1009
+ (field1?.hashCode() ?: 0) * 19
+ (field2?.hashCode() ?: 0) * 109
+ (field3?.hashCode() ?: 0)
override fun toString() = "MyClass(field1=$field1, field2=$field2, field3=$field3)"