How to override equals (), hashcode () and compareTo () for HashSet
I am trying to override the mentioned methods for mine HashSet
:
Set<MyObject> myObjectSet = new HashSet<MyObject>();
MyObject:
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
Map<String,String> myMap;
public MyObject(String name, int number, Map<String,String> myMap) {
this.name = name;
this.number = number;
this.myMap = myMap;
}
[...]
}
How to override hashcode (), equals () and compareTo () method?
I currently have the following:
public int hashCode () {
return id.hashCode();
}
// override the equals method.
public boolean equals(MyObject s) {
return id.equals(s.id);
}
// override compareTo
public int compareTo(MyObject s) {
return id.compareTo(s.id);
}
I read that comparison by id is not enough, this object is a constant object for DB (see here ).
The name and number are not unique to all objects of this type.
So how do I override it?
Do I also need to compare the hashmap within it?
I am embarrassed. The object's only unique feature is the myMap, which is populated later in the lifecycle.
How to check if it is equal?
Based on all the answers, I changed the methods to the following
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap()));
}
It is not suitable for compareTo method, "this method is undefined for Map type
source to share
The main question here is, "How can you tell if two objects are equal to each other?"
This is a simple question for simple objects. However, with even more complex objects it becomes more and more difficult.
As stated in the original question:
The only unique thing about the object is the myMap, which is populated later in the lifecycle.
Given two instances of a type MyObject
, member variables myMap
must be compared with each other. This card is of type Map<String, String>
. Several questions immediately come to the question:
- How do keys and values determine equality?
- (do I need to compare key = value pair as one?)
- (or should only values compare with each other?)
- How does the order of the keys on the map affect equality?
- (should the keys in the list be sorted so that ABC is equivalent to BCA?)
- (or does 1-2-3 mean something other than 3-2-1?)
- How is upper / lower case different from value equality?
- Will these objects ever be stored in a Java HashSet or Java TreeSet ?
- (do you need to store the same object multiple times in the same collection?)
- (or are objects with the same hashcodes stored only once?)
- Will these objects ever require sorting as part of a List or Java Collection ?
- How should a comparison function order non-equal objects in a list?
- (how should the order of the keys determine whether an object will appear earlier or later in the list?)
- (how should the values determine the order, especially if several values are different?)
The answers to each of these questions will vary between applications. For this to be applicable to a wide audience, the following assumptions are made:
- To support deterministic comparison, the keys will be sorted.
- Values will be considered case sensitive
- Keys and values are inseparable and will be compared as one
- The map will be flattened into one line so the results can be easily compared
The beauty of using equals()
, hashCode()
and compareTo()
is that once hashCode()
performed well, other functions can be defined on the basis of hashCode()
.
With all this in mind, we have the following implementation:
@Override
public boolean equals(final Object o)
{
if (o instanceof MyObject)
{
return (0 == this.compareTo(((MyObject) o)));
}
return false;
}
@Override
public int hashCode()
{
return getKeyValuePairs(this.myMap).hashCode();
}
// Return a negative integer, zero, or a positive integer
// if this object is less than, equal to, or greater than the other object
public int compareTo(final MyObject o)
{
return this.hashCode() - o.hashCode();
}
// The Map is flattened into a single String for comparison
private static String getKeyValuePairs(final Map<String, String> m)
{
final StringBuilder kvPairs = new StringBuilder();
final String kvSeparator = "=";
final String liSeparator = "^";
if (null != m)
{
final List<String> keys = new ArrayList<>(m.keySet());
Collections.sort(keys);
for (final String key : keys)
{
final String value = m.get(key);
kvPairs.append(liSeparator);
kvPairs.append(key);
kvPairs.append(kvSeparator);
kvPairs.append(null == value ? "" : value);
}
}
return 0 == kvPairs.length() ? "" : kvPairs.substring(liSeparator.length());
}
All critical work is done internally hashCode()
. For sorting, the function compareTo()
must return a negative / zero / positive number - a simple value hashCode()
. And the function equals()
only needs to return true / false - a simple check, which compareTo()
is zero.
For further reading, there is Lewis Carroll's famous Dialogue on Foundations of Logic that tackles the core issue of equality:
https://en.wikipedia.org/wiki/What_the_Tortoise_Said_to_Achilles
And, as far as simple, simple grammatical constructions are concerned, at the beginning of chapter 6, "A good example of two" equal "sentences," Pig and Pepper, " from Alice in Wonderland:
The fish footman began by producing an excellent letter from his hand, and he passed it on to another, solemnly said: "For the duchess. An invitation to the queen to play croquet." The footman frog repeated in the same solemn tone: "From the queen. Inviting the duchess to play croquet." Then they both bowed deeply and their curls tangled.
source to share
This is what intellij default option gives
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
if (number != myObject.number) return false;
if (name != null ? !name.equals(myObject.name) : myObject.name != null) return false;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + number;
result = 31 * result + (myMap != null ? myMap.hashCode() : 0);
return result;
}
}
But since you said
The only unique thing about the object is the myMap, which gets brought in later in the lifecycle.
I would just keep myMap and omit the name and number (but this begs the question, why would you include redundant name and number in all elements of your collection?)
Then it becomes
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
}
Be aware that there are other ways for equals and hashcode methods. For example, here are the parameters that intelliJ gives for code generation
To answer a further question about CompareTo
Unlike Equals and Hashcode, there is no contract between compareTo and any other behavior. You don't need to do anything with compareTo until you want to use it for, say, sorting. To read more about CompareTo Why will Java class be matched?
source to share
compareTo()
refers to sorting. It has nothing to do with HashSet
or HashMap
.
Working properly equals()
and hashCode()
vital to members of hash-based collections. Read their specifications in the Javadoc for Object
.
Perhaps the final guidelines for their implementation are in Joshua Bloch Effective Java. I recommend reading the relevant chapter - it's easy to Google. There is no point in trying to rephrase all of this here.
One thing that may have escaped your notice is that your field myMap
has its own equals()
and hashCode()
therefore you don't need to do anything with it. If you can guarantee that none of the fields are null, it makes sense hashCode()
(after Bloch's system):
public int hashCode() {
int result = 44; // arbitrarily chosen
result = 31 * result + (int) (id ^ (id >>> 32));
result = 31 * result + name.hashCode();
result = 31 * result + number;
result = 31 * result + myMap.hashCode();
return result;
}
(You will need more code if any of them can be null)
Almost all IDEs automatically generate both equals()
, and hashCode()
using all the fields in the class. They will use something very similar to Bloch's recommendations. Hunt around the user interface. You will find it.
Another option is to use Apache ReflectionUtils, which allows you to simply use:
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(final Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
This determines which fields to use at runtime and applies Bloch methods.
source to share
If you want to myMap
implement comparable and whatever other methods you want, create a decorator that implements the comparable interface and delegate all other methods to the wrapper myMap
.
public class ComparableMap implements Map<String, String>, Comparable<Map<String, String>> {
private final Map<String, String> map;
public ComparableMap(Map<String, String> map) {
this.map = map;
}
@Override
public int compareTo(Map<String, String> o) {
int result = 0;
//your implementation based on values on map on you consider one map bigger, less or as same as another
return result;
}
@Override
public boolean equals(Object obj) {
return map.equals(obj);
}
@Override
public int hashCode() {
return map.hashCode();
}
// map implementation methods
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
@Override
public String get(Object key) {
return map.get(key);
}
@Override
public String put(String key, String value) {
return map.put(key, value);
}
@Override
public String remove(Object key) {
return map.remove(key);
}
@Override
public void putAll(Map<? extends String, ? extends String> m) {
map.putAll(m);
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<String> keySet() {
return map.keySet();
}
@Override
public Collection<String> values() {
return map.values();
}
@Override
public Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
}
You can use this card anywhere you use myMap
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
ComparableMap myMap;
public MyObject(String name, int number, Map<String, String> myMap) {
this.name = name;
this.number = number;
this.myMap = new ComparablemyMap(myMap);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap())); //now it works
}
}
source to share