Marc Knaup
10/13/2020, 12:12 AMa.id == b.id
)
• both entities are completely equal (a.id == b.id && a.prop1 == b.prop1 && …
)
I can’t decide whether I should implement equals
as comparing merely IDs or comparing entire entities.
1️⃣ Equality by entity IDs
• If compare IDs I cover common cases like if (sender != recipient) sendNotification(…)
and it reads very natural.
• It’s also more performant because in most cases you don’t need to compare entire entities.
• For Set<Entity>
typically also the ID is compared, meaning you want to have one entry per user and not one entry per “state” per user.
• This can be confusing however if the entity is using data class
. Most developer would expect by-property equality for data classes.
2️⃣ Equality by entity properties
• If I compare entire entities I can do neat things like checking if datasets have changed. If I have a val old: Collection<User>
and val new: Collection<User>
and want to check if there have been any updates between the two I can simply use old != new
. That’s useful for example for Flows and distinctUntilChanged()
.
• Developers may however run into unexpected behavior if for some reason sender
and recipient
refer to the same user (by ID) but one of the two instances has slightly different properties (e.g. due to a prior update). Because sender != recipient
reads so natural, that can be very unexpected.
There is a third option where you use either 1️⃣ or 2️⃣ and provide the other one as an additional entity function. That gets tricky however when you’re in a generic context and may not know enough about the type to call such a function, e.g. in a Flow<Collection<T>>.distinctUntilChanged { old, new -> … }
.
I’m curious, how do you usually do that in your codebases?travis
10/13/2020, 8:06 AMDevelopers may however run into unexpected behavior if for some reasonMy 2 cents (I voted for 2️⃣): I think your point about one of the items having been updated "due to a prior update" illustrates why I actually findandsender
refer to the same user (by ID) but one of the two instances has slightly different properties (e.g. due to a prior update).recipient
sender != recipient
to be expected. As they are representing the objects at points in time; so when I reason about it, it makes sense that they are not the same (despite having the same ID).
More importantly, it more closely matches standard behavior of `data class`es so is more familiar/expected in that regard. Ultimately, go with whatever behavior most closely matches the standard language features. Things that diverge from that behavior deserve their own equality checking functions.
If you can avoid overriding equals
altogether (and let `data class`es do the heavy lifting for you) then that is preferred. If you mess up the equals implementation then you can get subtle bugs within collections. Just another avenue of introducing risk.marcinmoskala
10/13/2020, 8:48 AMsender.id == recipent.id
). Having full object comparison should be there as it is very useful in many cases, especially in tests (assertEquals
) or checks if object update should be propagated.