Don't you think that 5 rules enumerated in `equals...
# announcements
g
Don't you think that 5 rules enumerated in
equals
doc are not full? Look at this code:
Copy code
class MyClass {
    override fun equals(other: Any?): Boolean {
        return other != null
    }
}
1. Reflexive: for any non-null value x, x.equals(x) should return true. 2. Symmetric: for any non-null values x and y, x.equals(y) should return true if and only if y.equals(x) returns true. 3. Transitive: for any non-null values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true. 4. Consistent: for any non-null 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. 5. Never equal to null: for any non-null value x, x.equals(null) should return false. But this code leads to the strange behavior:
Copy code
println(MyClass() == Any()) // true
println(Any() == MyClass()) // false
r
This violates symmetry and transitivity
Those requirements apply to all pairs
x
and
y
of any types, not just of the same type
g
@randomcat if so, gimme X Y where X.equals(Y) is true, but Y.equals(X) is false
r
Any()
and
MyClass()
g
I can't call the same equals method on objects of different types
r
sure you can
it's all Any::equals
the contract is on the behavior of the expression, not on any particular override of the method
g
So, when I implement my own equals method, I should be aware about how parents' equals methods are implemented to not to violate contract
Because my own equals method matches all 5 rules
r
You have to consider every other equals method implementation. Reasonable implementations only compare equal with things of the same type or that implement the same interface (like List or Map, which have AbstractList and AbstractMap to handle equality for you).
Your implementation does not satisfy the contract because equals can be called providing any object as an argument, not just one of the same type.
g
So when we implement equals method we need: 1) To know implementation of all parents classes to guarantee that when Any.equals(MyClass) returns false MyClass.equals(Any) returns false as well (Any.equals(MyClass) may return true, it's not forbidden by contract) OR 2) Just add 6th rule that all normal equals functions have already implemented: For any non-null values x, y where y is not subtype of x, x.equals(y) returns false
r
Your type, in coordination with every other type in existence, must ensure that you uphold the equals contract. For 1) sometimes supertypes impose additional requirements on the behavior of subtype equals (again, like List). For 2) that is most definitely not the case. An empty ArrayList should compare equal to an empty LinkedList, but neither is a subtype of the other.
g
Yeah, you are right, my rule is applicable for most classes, but not for all.
So that means we should violate rules of inheritance and implement equals relying on supertype's implementation
v
Your implementation does not satisfy the contract, why do you say that?
You even showed one of the problematic cases yourself in your question
2. says
x.equals(y)
should return
true
if and only if
y.equals(x)
returns
true
. And as
Any().equals(MyClass())
will never return
true
, your implementation must not return
true
for
MyClass().equals(Any())
or it would violate rule 2
So basically returning
true
for anything that is not of your exact same type (also not a sub- or sibling type) is wrong, except if some class adds to the rules for sub classes, so that e. g. all empty lists might be considered equal, independent from the implementation class.
f
The interface mixes partial equality with total equality, as such a lot of implementations are legal that we cannot attest solely based on the type system. Only the implementation and/or docs can tell. Equals against other types isn't necessarily wrong, but at least seldom. Python, PHP, are doing this on a regular basis and it's often terribly confusing.