Not really. While immutability is great when you structure your program to support it, it’s not necessary and in some cases not even a good idea.
You don’t strictly have to, but it’s considered good practise and since you can auto generate it, it’s not even much work.
I don’t like this article. It seems to make this big distinction between a == b
and a.equals(b)
but this is not really true.
As the article corectly stated there are 3 different ways to do equality checks in kotlin (on a syntax level)
a == b
a.equals(b)
a === b
When we look at functionality this is no longer true. Kotlin will replace a == b
with a?.equals(b) ?: a === b
which is just a.equals(b)
in a null safe way. So there is no difference between a == b
and a.equals(b)
. The 1 exception to that are primitive types(Int
, Float
, etc). They will be replaced with the special jvm comparison instruction of their number type (which as pointed out in the article is different than the implementation of the function Float.equals(float)
, but in general you want to use the special instruction anyways).
Therefor in kotlin it is consider bad practice to use a.equals(b)
. Unless you break the contract of the equals
method(transitive, reflexive and symetrical, a value never equals null) it always behaves the same(float is an exception, but not a relevant one).
Now to data class vs custom equals implementation. It doesn’t matter. You can go either way and get the same outcome. Kotlin just generates the equals
method for you when you use data classes.
My advice use them whenever possible. They just make live easier for you.
If you can’t and you still need equals there are a few rules that you should follow. They are defined by the function contract described here:
- It is reflexive : for any non-null reference value
x
,x.equals(x)
should returntrue
.- It is symmetric : for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
.- It is transitive : for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
.- It is consistent : for any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used inequals
comparisons on the objects is modified.- For any non-null reference value
x
,x.equals(null)
should returnfalse
.The
equals
method for classObject
implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference valuesx
andy
, this method returnstrue
if and only ifx
andy
refer to the same object (x == y
has the valuetrue
).Note that it is generally necessary to override the
hashCode
method whenever this method is overridden, so as to maintain the general contract for thehashCode
method, which states that equal objects must have equal hash codes.
While it’s considered good practice to implement hashCode, it’s not enforced althoug classes like HashMap
or LinkedHashSet
(default implementations behind mapOf
and setOf
) might not work properly if you don’t.
Than implementing equals
/data classes together with ==
is exactly what you are looking for. If you are looking for reference equality ===
is what you need. Also non data classes that don’t implement equals
inherit the default version from Any
(java Object
) which implements reference equality.
TL;DR
- Use data classes when you care about equality
- If not possible implement your own equals/hashCode methods (Idea has a good code generator for that)
- If you don’t care about equality, do whatever you want
- Always use
a == b
instead ofa.equals(b)
for equality - use
a === b
for reference equality (data saved at same memory location)