Overriding Object.Equals VS Overloading It

Overriding Object.equals VS Overloading it

This is because overloading the method won't change the behavior in places like collections or other places that the equals(Object) method is explicitly used. For example, take the following code:

public class MyClass {

public boolean equals(MyClass m) {
return true;
}
}

If you put this in something like a HashSet:

public static void main(String[] args) {
Set<MyClass> myClasses = new HashSet<>();
myClasses.add(new MyClass());
myClasses.add(new MyClass());
System.out.println(myClasses.size());
}

This will print 2, not 1, even though you'd expect all MyClass instances to be equal from your overload and the set wouldn't add the second instance.

So basically, even though this is true:

MyClass myClass = new MyClass();
new MyClass().equals(myClass);

This is false:

Object o = new MyClass();
new MyClass().equals(o);

And the latter is the version that collections and other classes use to determine equality. In fact, the only place this will return true is where the parameter is explicitly an instance of MyClass or one of its subtypes.


Edit: per your question:

Overriding versus Overloading

Let's start with the difference between overriding and overloading. With overriding, you actually redefine the method. You remove its original implementation and actually replace it with your own. So when you do:

@Override
public boolean equals(Object o) { ... }

You're actually re-linking your new equals implementation to replace the one from Object (or whatever superclass that last defined it).

On the other hand, when you do:

public boolean equals(MyClass m) { ... }

You're defining an entirely new method because you're defining a method with the same name, but different parameters. When HashSet calls equals, it calls it on a variable of the type Object:

Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

(That code is from the source code of HashMap.put, which is used as the underlying implementation for HashSet.add.)

To be clear, the only time it will use a different equals is when an equals method is overridden, not overloaded. If you try to add @Override to your overloaded equals method, it will fail with a compiler error, complaining that it doesn't override a method. I can even declare both equals methods in the same class, because it's overloading:

public class MyClass {

@Override
public boolean equals(Object o) {
return false;
}

public boolean equals(MyClass m) {
return true;
}
}

Generics

As for generics, equals is not generic. It explicitly takes Object as its type, so that point is moot. Now, let's say you tried to do this:

public class MyGenericClass<T> {

public boolean equals(T t) {
return false;
}
}

This won't compile with the message:

Name clash: The method equals(T) of type MyGenericClass has the same erasure as equals(Object) of type Object but does not override it

And if you try to @Override it:

public class MyGenericClass<T> {

@Override
public boolean equals(T t) {
return false;
}
}

You'll get this instead:

The method equals(T) of type MyGenericClass must override or implement a supertype method

So you can't win. What's happening here is that Java implements generics using erasure. When Java finishes checking all the generic types on compile time, the actual runtime objects all get replaced with Object. Everywhere you see T, the actual bytecode contains Object instead. This is why reflection doesn't work well with generic classes and why you can't do things like list instanceof List<String>.

This also makes it so that you can't overload with generic types. If you have this class:

public class Example<T> {
public void add(Object o) { ... }
public void add(T t) { ... }
}

You'll get compiler errors from the add(T) method because when the classes are actually done compiling, the methods would both have the same signature, public void add(Object).

overloading or overriding equals() method

Overriding is when you implement a method that has been declared in a superclass. To qualify as an override, the signature of the override must match (within some tolerance) the signature of the method being overridden. ​

Overloading is when you have multiple methods with the same name but different signatures.

In the presence of overloads, which one is called is determined solely by the static types of the arguments at the call site. In your example, you have two overloads of equals:

boolean equals(Object o) { ... }
boolean equals(Person p) { ... }

When you call:

Person p = ...
... equals(p) ...

the overload selection process proceeds as follows:

  • Determine the static types of the arguments.
  • Determine which overloads are applicable (using arity, subtyping, conversion, etc.)
  • If more than one is applicable, determine the most specific one.
  • If two or more are equally specific, a compile-time error is issued.

Here, the argument type is Person, and both overloads are applicable (since a Person is an Object), but equals(Person) is more specific, so that one is called.

If we changed the story slightly:

Object p = new Person(...);
... equals(p) ...

Now, the static type of p is Object, so only the first overload -- equals(Object) -- is applicable. All we know is that it is an Object; that it happens to hold a Person is something that we don't know statically. (We could find it out dynamically with instanceof.)

To summarize:

  • Expressions have both a static (compile-time) and dynamic (run-time) type;
  • Overload selection is done purely on the basis of static types.

Now, you probably don't want to declare equals(Person) for the same problems you're seeing here -- it looks like an override, but really its an overload, and won't get called when you think it does.

Is overloading equals worthwhile

I'dont see the case for overloading equals, except that is more error-prone and harder to maintain, especially when using inheritance.

Here, it can be extremly hard to maintain reflexivity, symmetry and transitivity or to detect their inconsistencies, because you always must be aware of the actual equals method that gets invoked. Just think of a large inheritance hierarchie and only some of the types implementing their own overloading method.

So I'd say just don't do it.

When would == be overridden in a different way to .equals?

My question is: why have them both (I realise there must be a very good reason)

If there's a good reason it has yet to be explained to me. Equality comparisons in C# are a godawful mess, and were #9 on my list of things I regret about the design of C#:

http://www.informit.com/articles/article.aspx?p=2425867

Mathematically, equality is the simplest equivalence relation and it should obey the rules: x == x should always be true, x == y should always be the same as y == x, x == y and x != y should always be opposite valued, if x == y and y == z are true then x == z must be true. C#'s == and Equals mechanisms guarantee none of these properties! (Though, thankfully, ReferenceEquals guarantees all of them.)

As Jon notes in his answer, == is dispatched based on the compile-time types of both operands, and .Equals(object) and .Equals(T) from IEquatable<T> are dispatched based on the runtime type of the left operand. Why are either of those dispatch mechanisms correct? Equality is not a predicate that favours its left hand side, so why should some but not all of the implementations do so?

Really what we want for user-defined equality is a multimethod, where the runtime types of both operands have equal weight, but that's not a concept that exists in C#.

Worse, it is incredibly common that Equals and == are given different semantics -- usually that one is reference equality and the other is value equality. There is no reason by which the naive developer would know which was which, or that they were different. This is a considerable source of bugs. And it only gets worse when you realize that GetHashCode and Equals must agree, but == need not.

Were I designing a new language from scratch, and I for some crazy reason wanted operator overloading -- which I don't -- then I would design a system that would be much, much more straightforward. Something like: if you implement IComparable<T> on a type then you automatically get <, <=, ==, !=, and so on, operators defined for you, and they are implemented so that they are consistent. That is x<=y must have the semantics of x<y || x==y and also the semantics of !(x>y), and that x == y is always the same as y == x, and so on.

Now, if your question really is:

How on earth did we get into this godawful mess?

Then I wrote down some thoughts on that back in 2009:

https://blogs.msdn.microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/

The TLDR is: framework designers and language designers have different goals and different constraints, and they sometimes do not take those factors into account in their designs in order to ensure a consistent, logical experience across the platform. It's a failure of the design process.

When would == be overloaded in a different way to how .equals is overridden?

I would never do so unless I had a very unusual, very good reason. When I implement arithmetic types I always implement all of the operators to be consistent with each other.

If I overload equals should I still override haschode in java?

No Hash-based collection will ever use your overloaded equals() method. It's your method, and you decide what it should do. The contract is thus entirely yours.

But I'll repeat what you said in your question: you shouldn't overload equals() in the first place. If you do, you should at least make it consistent with the actual equals(Object) method to avoid most confusions. And since it should be consistent with equals(), that means that you need to override equals(Object), and thus also override hashCode().

Eclipse Xtend: Overriding equals() vs. operator_equals()

If your goal is to change "equal to" behavior, then you should simply override equals (and hashCode).

You would only define operator_equals if you want it to have different behavior than equals. But you generally wouldn't want that.

If you overload the == operator, the original equals method still exist and some some code might use the operator while other code will use equals (for example Java libraries). This sounds like a good way to break something.

It makes sense for Xtend to provide the ability to overload all operators for some odd use cases or even just for consistency, but in my opinion redefining existing operator behavior is bad practice and should be avoided. It's much safer to only overload operators that are not yet defined for a type.

Overloading of equals() method of Object class

All the collections will call it like this. Suppose you have a list of Moofs. The list is a generic class. It doesn't even know about the types of the objects it stores. So if you try to test whether a moof is contained in the list, you'll call

list.contains(moof)

And the list will call moof.equals(theObjectStoredAtIndex0), moof.equals(theObjectStoredAtIndex1), etc. until it finds one that is equal. The only thing that the list knows about theObjectStoredAtIndex0is that it is of type Object. So it needs equals() to take an Object as argument.

Even you might do it. Suppose Moof implements Baz, and Maaf also implements Baz. And suppose you want to test if baz1 is equal to baz2. You'll do

if (baz1.equals(baz2))

The compiler has no idea of the concrete type of baz1 and baz2. All it knows is that they are both instances of Object, and thus calls, polymorphically, the Moof.equals(Object) method.

Equals override and == overload, for value objects vs entities

Motivation

Proper equality operations are among the most underestimated tools in the object-oriented world in my opinion. Yes, you should absolutely implement them where it makes sense, it will make your program a lot more concise.

E.g. compare

Assert.Equal(expectedAddress.Street, address.Street);
Assert.Equal(expectedAddress.City, address.City);
Assert.Equal(expectedAddress.Zip, address.Zip);
Assert.Equal(expectedAddress.State, address.State);
Assert.Equal(expectedAddress.Country, address.Country);

with

Assert.Equal(expectedAddress, address);

This becomes even more extreme when you have deeply nested value objects.

When to use

To not produce awkward behavior, only implement equality operations on immutable types. This is important, because e.g. hash maps will not function correctly with mutable types (think about what happens when an object's hash code changes while it is in a hash map).

Implementing Equals alone could make sense for some mutable types, but is generally discouraged, e.g. by a Microsoft code analysis rule.

Value Objects

Equality operations are most useful on value objects. Also override the equality operators to make the equality comparison look natural.

The implementation of the equality operations is trivial: Consider all data fields but disregard computed properties. This will create purely content based equality operations.

Since implementing equality operations on value objects is mechanistic, there is a library called Equ that does this automatically for you (which I wrote myself). It will create equality operations at static instantiation time that have the same runtime performance as manually written Equals and GetHashCode implementations.

Entities

With entities, it gets a bit more tricky. The problem is that it is normally not really clear what equality means from a domain point of view.

Clearly, two Customer entities with different IDs are not equal. But that's about it. Are two Customer entities with the same ID, but in different state equal? Difficult question.

The good news is that this kind of functionality in not really required. So my advice is: Don't implement equality operations on entities.

Overloading operator== versus Equals()

I believe the standard is that for most types, .Equals checks object similarity, and operator == checks reference equality.

I believe best practice is that for immutable types, operator == should be checking for similarity, as well as .Equals. And if you want to know if they really are the same object, use .ReferenceEquals. See the C# String class for an example of this.

Why does Java's Area#equals method not override Object#equals?

RealSkeptic linked to JDK-4391558 in a comment above. The comment in that bug explains the reasoning:

The problem with overriding equals(Object) is that you must also
override hashCode() to return a value which guarantees that equals()
is true only if the hashcodes of the two objects are also equal.

but:

The problem here is that Area.equals(Area) does not perform a very
straight-forward comparison. It painstakingly examines each and every
piece of geometry in the two Areas and tests to see if they cover the
same enclosed spaces. Two Area objects could have a completely
different description of the same enclosed space and equals(Area)
would detect that they were the same.

So basically we're left with an array of not-so-pleasant options, such as:

deprecate equals(Area) and create an alternate name for that
operation, such as "areasEqual" so as to avoid the confusion.
Unfortunately, the old method would remain and would be linkable and
would trap many people who were intending to invoke the equals(Object)
version.

or:

deprecate equals(Area) and change its implementation to be exactly
that of equals(Object) so as to avoid semantic problems if the wrong
method is called. Create a new method with a different name to avoid
confusion to implement the old functionality provided by equals(Area).

or:

implement equals(Object) to call equals(Area) and implement a dummy
hashCode() which honors the equals/hashCode contract in a degenerate
way by returning a constant. This would make the hashCode method
essentially useless and make Area objects nearly useless as keys in a
HashMap or Hashtable.

or other ways to modify the equals(Area) behavior that would either change its semantics or make it inconsistent with hashCode.

Looks like changing this method is deemed by the maintainers to be neither feasible (because neither option outlined in the bug comment quite solves the problem) nor important (since the method, as implemented, is quite slow and would probably only ever return true when comparing an instance of an Area with itself, as the commenter suggests).



Related Topics



Leave a reply



Submit