When creating a new class, should we always override the equals
and hashCode
even if we don’t intent at that point to use the class with any Collection classes?
Or is it better to wait till such a need arises before overriding equals
and hashCode
so as to be sure on what the logical equality should be?
7 Answers
should we always override the equals and hashCode even if we don’t intent at that point to use the class with any Collection classes?
No and I would go even further as to say you probably don't need to override them even if you are going to put them into a collection. The default implementations are perfectly compatible with collections as is. For lists, it's irrelevant anyway.
You should override equals if and only if your objects have a logical identity that is independent of their physical identity. In other words, if you want to have multiple objects that represent the same entity. Integer, is a good example of this. The integer 2 is always 2 regardless of whether there are 100 instances or 1 of the Integer object with the value of 2.
You must override hashcode if you've overridden equals.
That's it. There are no other good reasons to modify these.
As a side note, the use of equals to implement algorithms is highly overused. I would only do this if your object has a true logical identity. In most cases, whether two objects represent the same thing is highly context dependent and it's easier and better to use a property of the object explicitly and leave equals alone. There are many pitfalls to overriding equals, especially if you are using inheritance.
An example:
Let's say you have an online store and you are writing a component that fulfills orders. You decide to override the equals()
method of the Order
object to be based on the order number. As far as you know, that's the it's identity representation. You query a DB every so often and create objects from the response. You take each Order object and keep it as a key in a set which is all the orders in process. But there's a problem, orders can be modified in a certain time frame. You now might get a response from the DB that contains the same Order number but with different properties.
You can't just write this object to your set because you'll lose track of what was in process. You might end up processing the same order twice. So, what are the options? You could update the equals()
method to include a version number or something additional. But now you have to go through your code to figure out where you need to base the logic on the having the same order number and where you need it to be based on the new object identity. In other words, there's not just one answer to whether two objects represent the same thing. In some contexts it's the order, and in some contexts it's the order and the version.
Instead of the set, you should build a map and use the order number as the key. Now you can check for the existence of the key in the map and retrieve the other object for comparison. This is much more straightforward and easy to understand than trying to make sense of when the equals method works they way you need it to for different needs.
A good example of this kind of complexity can be found in the BigDecimal class. You might think that BigDecimal("2.0")
and BigDecimal("2.00")
are equal. But they are not.
Conclusion:
It can make sense to implement equals()
(make sure you update hashcode()
too if you do) and doing so doesn't prevent the use of any of the techniques I describe here.
But if you are doing this:
- Make sure your object is immutable
- Use every meaningful property of the object to define equals.
- If you are using inheritance, either:
- make equals final on the base class OR
- you must check that the actual type matches in subclasses
If any of these aren't followed, you are in for trouble.
-
Comments are not for extended discussion; this conversation has been moved to chat.– maple_shaft ♦Commented Oct 7, 2019 at 20:33
In Java, if you don’t override anything, equality is defined as object references being equal, and the hash code is the bit pattern of the reference. That works just fine to put objects into containers.
And there are objects where this is exactly right. For example an object representing a window in the UI. Two windows are equal if and only if they are the same window.
PS. It makes absolutely sense to have a set of windows, or a dictionary with windows as keys, or an array of windows and lookup if the array contains some window - so you need to be able to compare windows for equality.
-
Your example already assumes that there would be a need to check for equality between two window objects. What I guess am not sure about is, for which cases there is absolutely no reason to have any equality checked and vice versa– JimCommented Oct 3, 2019 at 18:30
-
1@Jim It's perfectly valid to use the Window objects as keys in a map or as items in a set. For example, you might want to add a Window to a collection and not have to check to see if it's already there. Commented Oct 3, 2019 at 20:00
-
@JimmyJames: You are describing a case though that the
equals
has not been implemented hence the key comparison will be identity comparison. So when would a good example be for implementing non-identity comparison for objects that are like "Window" objects i.e. non-POJOs?– JimCommented Oct 3, 2019 at 20:14 -
-
The short answer is; it depends.
As you correctly suggest, I recommend letting the usage scenario dictate if equals
and hashCode
should be implemented or not. And there are cases where it is required and cases where it will likely cause headaches and problems.
Here are two examples of such scenarios.
Incorrectly implemented equals and hashCode when using Sets
Imagine a Person-object with a first and last name, where only the last name is used in equals
and hashCode
. The following scenario could then cause bugs in your code:
final Set<Person> people = new HashSet<>();
people.add(new Person("John", "Anderson"));
people.add(new Person("Bob", "Anderson"));
System.out.println(people);
The result (might surprise you):
[Person{firstName='John', lastName='Anderson'}]
I thought the result would be Bob Anderson, but I think the takeaway here is that different implementations of
Set
might actually do this differently, so your objects better be identical with regards to what you put inequals
andhashCode
...
And even if you do add firstName
to the equals
and hashCode
, maybe your program would later have to handle two Bob Anderson with different addresses or phone numbers, so the bug could come back.
If you still wanted to keep your people in a Set and couldn't get away with removing equals
and hashCode
, you would have to come up with some alternative value to use for uniqueness. Without a primary key from a database, a UUID might be a candidate.
Keys in Maps
Implementing equals
and hashCode
causes bugs in the above scenario, but they are needed if the object should be the key in a Map
.
Consider the following scenario (where, in order to fix the above bug, we've removed all use of equals
and hashCode
):
final Map<Person, String> notes = new HashMap<>();
notes.put(new Person("John", "Anderson"),
"Isn't he that guy in The Matrix?");
System.out.println("Notes: " + notes.get(new Person("John", "Anderson")));
The result:
Notes: null
Because the default implementation of equals
and hashCode
uses the reference (I actually think it's the memory address) and we recreated the object, even if it has the exact same data, it's two different objects with two different addresses that will be seen as two different keys by Map
.
And solving this problem by trying to always use the same key object is, of course, going to cause headaches somewhere down the road...
I've found in most cases I can get away with not using complex objects as map keys and instead fall back on Strings and ints and similar types with predefined (and well-defined) behavior in regard to equality.
Sets and Maps
If you look at the implementation of different Sets you'd notice that in most, if not all, cases a Set is a Map in disguise. The keySet of a Map has the exact same functionality as a Set.
So, the above two scenarios comes down to knowing what behavior you need from using your object in a Set.
Of course, other frameworks and classes might require or bug out on the presence or absence of equals and hashCode in similar or different ways than the above two scenarios, but in many cases it might depend on the use of Sets and Maps in them as well.
Databases
It also seems, Sets and Maps are quite rare in most Java programs because a lot of them deals with database stored data where the behavior from Sets and Maps would be implemented in the database instead (e.g. uniqueness of values or mapping keys to data).
However, even when dealing with databases, you might need to think a bit extra about if you need to implement equals and hashCode or not.
-
Model data used with ORM frameworks (e.g. Hibernate) may be stored in Sets and Maps internally. Overriding
equals
andhashCode
can become a thorn unless implemented correctly since these methods are used by ORM frameworks. The trick is to implement them against fields that are immutable with the row in the table. Better yet, don't override them at all and rely on surrogate UUID keys.– danCommented Nov 5 at 15:03
NO, We don't need to.
These methods make sense mostly on data classes or POJO classes which act like data holders. However, as your requirement grows you may feel a need to compare your objects for sorting, finding duplicates etc. Then you should add the equality method. But till those requirements arise you don't need them. Premature optimization is the root of all evils. No one will kill you to open the class on a later stage and add 2 methods. That's perfectly legal and allowed.
However, if ever you decide to override either of them you must override the other for reasons well-known.
-
Could you elaborate a bit on the need for objects that are not POJOs?– JimCommented Oct 3, 2019 at 20:11
-
Not all classes are POJOs. For example take the case of View or Controller classes in MVC, classes doing complex arithmetic computations, classes providing abstraction to more low-level functionalities. This list can go on and on. And in majority of these cases you dont need the equality method– SisirCommented Oct 3, 2019 at 20:18
Two examples where you wouldn’t override or implement anything at all:
In Swift you have the protocols Equatable, Comparable and Hashable. Your choice to support these protocols or not. If you don’t support Equatable and Hashable then you can’t put instances of your class into a dictionary or set. If you don’t support Equatable then you can’t for example lookup an item in an array. If you don’t support Comparable then you can’t sort an array. So you just don’t support Hashable, and the first developer who needs it will have to add the code.
In Objective-C there is an implementation in the baseclass NSObject which compares pointers to instances for equality and uses the pointer to an instance as the hash value. So you can put objects into a hash table.
Or is it better to wait till such a need arises before overriding equals and hashCode to be sure of what the logical equality Should be?
This.
However, it's worth thinking if, by overwriting .equals
, we only pretend to know if the current object is the same as a second one. This can be confusing but (to me) same and equals, in Java, often have different meanings and purposes.
Overwriting .equals()
to know if the current Order
is the same as a second one can be dangerous because of the many things in Java that leverage on .equals()
.
If I have a business rule that requires to know if a given Order
is the same as a second one, probably a .is(Order)
or .same(Order)
method comparing the key values / IDs is a better choice, after all, it's a business rule and its content will be driven by business needs more than implementation details of the programming language.
I don't know how other Java developers approach cases like this but I only overwrite .equals
and hashcode
when I use very concrete collections, sets or maps and, I want to be sure that the comparison will do something more than comparing pointers. And, of course, whenever I overwrite these two methods, I write a lot of unit tests to ensure the proper behaviour when added to containers or used by the business logic.
You don't technically need to. If you know that you won't be putting instances of these objects into any of the Collection classes or any other cases where these methods may be called, you don't need to.
However, I would point out that, in most environments, it's trivial to generate these. IDEs such as Eclipse, IntelliJ, and NetBeans have the ability to automatically generate these methods based on attributes or other methods in the class. There may be other tools that can help with this as well, to ensure that the implementation of these methods meet the contract.
If the classes are publicly exposed and you have the capability to automatically generate and refresh them, I would simply because it's low-hanging fruit and people won't need to check to see an appropriate implementation in the future - they will get what they expect. But if it's an internal class, I'd leave that up to you.
toString()
method, you might be able to get away with using that in your comparison. e.g.public int hashCode() { return this.toString().hashCode(); }
good toString()
method for you. UsuallytoString
has formatting and many characters that are the same for all instances which make it a very bad candidate for a hashCode