Non Data Class == Member Comparison

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)

  1. a == b
  2. a.equals(b)
  3. 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 return true .
  • It is symmetric : for any non-null reference values x and y , x.equals(y) should return true if and only if y.equals(x) returns true .
  • It is transitive : for any non-null reference values x , y , and z , if x.equals(y) returns true and y.equals(z) returns true , then x.equals(z) should return true .
  • It is consistent : for any non-null reference values x and y , multiple invocations of x.equals(y) consistently return true or consistently return false , provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x , x.equals(null) should return false .

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y , this method returns true if and only if x and y refer to the same object ( x == y has the value true ).

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode 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 of a.equals(b) for equality
  • use a === b for reference equality (data saved at same memory location)