Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird behavior of java.util.Set.contains(Object o)

The doc about java.util.Set.contains(Object o) says:

Returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e)).

That said, here is a POJO (as you can see, I overwrote its equals method):

public class MonthAndDay {

    private int month;
    private int day;

    public MonthAndDay(int month, int day) {
        this.month = month;
        this.day = day;
    }

    @Override
    public boolean equals(Object obj) {
        MonthAndDay monthAndDay = (MonthAndDay) obj;
        return monthAndDay.month == month && monthAndDay.day == day;
    }

}

So please, why does the following code prints false instead of true?

Set<MonthAndDay> set = new HashSet<MonthAndDay>();
set.add(new MonthAndDay(5, 1));
System.out.println(set.contains(new MonthAndDay(5, 1)));
// prints false

A solution is to rewrite the contains(Object o) method, but the original one should be (almost) exactly the same, am I wrong?

Set<MonthAndDay> set = new HashSet<MonthAndDay>() {

    private static final long serialVersionUID = 1L;

    @Override
    public boolean contains(Object obj) {
        MonthAndDay monthAndDay = (MonthAndDay) obj;
        for (MonthAndDay mad : this) {
            if (mad.equals(monthAndDay)) {
                return true;
            }
        }
        return false;
    }

};
set.add(new MonthAndDay(5, 1));
System.out.println(set.contains(new MonthAndDay(5, 1)));
// prints true
like image 789
sp00m Avatar asked Sep 03 '12 13:09

sp00m


People also ask

Does Set contain Object o method?

Explanation: Set has contains(Object o) method instead of get(Object o) method as get is needed for comparing object and getting corresponding value.

What does an Object contains in Java?

A Java object is a member (also called an instance) of a Java class. Each object has an identity, a behavior and a state. The state of an object is stored in fields (variables), while methods (functions) display the object's behavior. Objects are created at runtime from templates, which are also known as classes.

What is Set method in Java?

The set() method of Java Stack is used to replace any particular element in the stack created using the Stack class with another element. This can be done by specifying the position of the element to be replaced and the new element in the parameter of the set() method. Syntax: public E set(int index, Object element)

How contains method works internally in Java?

Collection contains() method in Java with ExamplesThis method returns a boolean value depicting the presence of the element. If the element is present, it returns true, else it returns false. Parameters: This method accepts a mandatory parameter element of type Object which is to be checked in this collection.


2 Answers

When you override equals(Object) you also need to override hashcode().

Specifically, the methods must be implemented so that if a.equals(b) is true, then a.hashcode() == b.hashcode() is all true. If this invariant is not respected, then HashMap, HashSet and Hashtable will not work properly.

The technical details of how hashcode() and equals(Object) should behave are specified in the Object API.


So why do the hash-based data structures break if you get this wrong? Well basically because a hash table works by using the value of the hash function to narrow down the set of values to be compared with the "candidate". If the hashcode for the candidate is different to the hashcode for some object in the table, then the chances are that the lookup algorithm won't compare with the object in the table ... even if the objects are equal.

like image 157
Stephen C Avatar answered Nov 09 '22 23:11

Stephen C


HashSet will only use equals() if the elements share the same hashCode(), thus you need to override both. Here's the relevant part of the code that is used by HashSet#contains() (note that HashSet is backed by a HashMap):

  355       /**
  356        * Returns the entry associated with the specified key in the
  357        * HashMap.  Returns null if the HashMap contains no mapping
  358        * for the key.
  359        */
  360       final Entry<K,V> getEntry(Object key) {
  361           int hash = (key == null) ? 0 : hash(key.hashCode());
  362           for (Entry<K,V> e = table[indexFor(hash, table.length)];
  363                e != null;
  364                e = e.next) {
  365               Object k;
  366               if (e.hash == hash &&
  367                   ((k = e.key) == key || (key != null && key.equals(k))))
  368                   return e;
  369           }
  370           return null;
  371       }

Not doing so, violates the contract of Object#hashCode(), which states that:

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

like image 7
João Silva Avatar answered Nov 09 '22 23:11

João Silva