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

+3


source to share


4 answers


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.

0


source


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

enter image description here

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?

+1


source


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.

0


source


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
        }

    }

      

0


source







All Articles