7

I am trying to override the mentioned methods for my 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 do I override the hashcode(), equals() and compareTo() method?


Currently I 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 comparing by id is not enough this is object is a persistent entity for the DB (see here).

The name and number aren't unique across all objects of this type.

So how should I override it?
Do I also need to compare the hashMap inside it?

I am confused. The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.

How do I check for its equality?

Based on all the responses I have 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()));
    }

This fails at the compareTo method, "this method is undefined for the type Map

user_mda
  • 18,148
  • 27
  • 82
  • 145
  • As you mentioned, the only unique thing about the object is the first entry in the map, I would think towards adding an extra field with that entry copied in it. That way it would be the real world candidate key and can be used to implement equals and hashcode methods. Can you also elaborate the life-cycle of this object w.r.t. when these methods would actually be called? – Hassan Jun 19 '17 at 20:58
  • 2
    We don't have enough information to answer your question. Where does that ID come from? I see no ID in `MyObject`. What is the primary key of your entity in the DB? – Guillaume F. Jun 19 '17 at 21:17
  • 3
    As written right now, this question is extremely unclear. Your code example for overriding are simply wrong (as they use the wrong parameter type for equals() for example). And then you are using an id field which isn't defined in your MyObject class?! Instead of putting up a bounty, you should focus on improving the question first. – GhostCat Jun 20 '17 at 02:57
  • https://stackoverflow.com/questions/2719877/object-equality-in-context-of-hibernate-webapp – shmosel Jun 20 '17 at 03:53
  • added the id field in the class. @GhostCat . Sure its wrong, and I am looking for the right thing to do – user_mda Jun 20 '17 at 13:40
  • If the map is the only unique thing, why not just use the map's `hashcode/equals/compareTo` methods? You don't want those values to change after you have added the object to a hashset either. – matt Jun 20 '17 at 14:55
  • So I overwrite the hashcode, equals, and compareTo method by comparing the map in this object? – user_mda Jun 20 '17 at 14:57

4 Answers4

1

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 the map myMap which gets populated later in the lifecycle.

I would just keep myMap and skip both name and number (But this begs the question, why would you include a redundant data- name and number in all the 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;
  }
}

Keep in mind that, there are other ways too for the equals and hashcode methods. For example, Here are the options that intelliJ gives for code generation

enter image description here

To Answer Further question about CompareTo

Unlike Equals and Hashcode, here is no contract exist between compareTo and any other behaviors. You don't really need to do anything with compareTo until you want to make use of it for say, sorting. To read more about CompareTo Why should a Java class implement comparable?

so-random-dude
  • 15,277
  • 10
  • 68
  • 113
1

compareTo() is relevant to sorting. It has no relevance to a HashSet or HashMap.

A properly working equals() and hashCode() are vital for members of hash-based collections. Read their specifications in the Javadoc for Object.

Possibly the definitive recommendations for implementing these are in Joshua Bloch's Effective Java. I recommend reading the relevant chapter -- it's easily Google-able. There's no point in trying to paraphrase it all here.


One thing that may have escaped your notice, is that your field myMap has a working equals() and hashCode() of its own, so you don't have to do anything special with it. If you can guarantee that none of the fields are null, a reasonable hashCode() would be (following 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'll need more code if any of these could be null)


Pretty much all IDEs will automatically generate both equals() and hashcode(), using all the fields in the class. They'll use something very similar to Bloch's recommendations. Hunt around the UI. You'll find it.

Another alternative 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 works out which fields to use at runtime, and applies Bloch's methods.

slim
  • 40,215
  • 13
  • 94
  • 127
1

The basic question here is "How can you determine if two objects are equal to each other?"

This is a simple question for simple objects. However, it becomes increasingly difficult with even slightly more complex objects.

As stated in the original question:

The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.

Given two instances of the type MyObject, the member variables myMap must be compared with each other. This map is of type Map<String, String>. A few questions immediately come to mind:

  • How do the keys & values define equality?
    • (does a key=value pair need to be compared as a unit?)
    • (or should only the values be compared to each other?)
  • How does the order of the keys in the map affect equality?
    • (should keys in the list be sorted, so that A-B-C is equivalent to B-C-A?)
    • (or does 1-2-3 mean something different than 3-2-1?)
  • Does upper/lower case make any different to the equality of the values?
  • Will these objects ever be stored in some kind of Java HashSet or Java TreeSet?
    • (do you need to store the same object several times in the same collection?)
    • (or should objects with equal hashcodes only be stored once?)
  • Will these objects ever require sorting as part of a list or Java Collection?
  • How should the comparison function arrange non-equal objects in a list?
    • (how should key order determine if an object will come earlier or later in a list?)
    • (how should values determine order, especially if several values are different?)

Answers to each of these questions will vary between applications. In order to keep this applicable to a general audience, the following assumptions are being made:

  • To maintain a deterministic comparison, keys will be sorted
  • Values will be considered to be case-sensitive
  • Keys and values are inseparable, and will be compared as a unit
  • The Map will be flattened into a single String, so results can be compared easily

The beauty of using equals(), hashCode(), and compareTo() is that once hashCode() is implemented properly, the other functions can be defined based on hashCode().

Considering all of that, 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 the critical work is being done inside of hashCode(). For sorting, the compareTo() function only needs to return a negative/zero/positive number -- a simple hashCode() diff. And the equals() function only needs to return true/false -- a simple check that compareTo() equals zero.


For further reading, there is a famous dialogue by Lewis Carroll on the foundations of logic, which touches on the basic question of equality:

https://en.wikipedia.org/wiki/What_the_Tortoise_Said_to_Achilles

And, in regard to even simple grammatical constructs, there is a fine example of two "equal" sentences at the start of chapter 6, "Pig and Pepper", from Alice in Wonderland:

The Fish-Footman began by producing from under his arm a great letter, and this he handed over to the other, saying, in a solemn tone, "For the Duchess. An invitation from the Queen to play croquet." The Frog-Footman repeated, in the same solemn tone, "From the Queen. An invitation for the Duchess to play croquet." Then they both bowed low and their curls got entangled together.

JonathanDavidArndt
  • 2,518
  • 13
  • 37
  • 49
0

If you want to make myMap implements comparable, and any other methods that you want, create decorator that implement comparable interface and delegate all other methods to enclosing myMap instance.

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 may use this map in anywhere where 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
        }

    }
fxrbfg
  • 1,756
  • 1
  • 11
  • 17