19

A HashSet only stores values ones, when the equals method says that they're the same. Thats what I thought.

But now i'm adding Elements to a HashSet where the equals method returns true and the size of the set still grows?? sorry I'm confused. Some hints where i'm wrong would be nice.

Element t1 = new Element(false, false, false, false);
Element t2 = new Element(true, true, true, true);
Element t3 = new Element(false, false, false, false);

if (t1.equals(t3))
    System.out.println("they're equal");

Set<Element> set = new HashSet<>();

set.add(t1);
set.add(t2);
set.add(t3);

System.out.println("set size: " + set.size());

so in this example my console output is:

they're equal
set size: 3

That makes no sense to me.. shouldn the size be 2?

tObi
  • 1,864
  • 3
  • 20
  • 32

4 Answers4

28

The problem is that your Element class has not overridden the equals and hashCode methods or these implementations are broken.

From Object#equals method javadoc:

The equals method implements an equivalence relation on non-null object references:

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true. It is consistent: for any non-null reference values x and y, multiple invocations of -x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

From Object#hashCode method javadoc:

The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
  • 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.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

Make sure the implementations of these methods satisfy these rules and your Set (backed by a HashSet) will work as expected.

Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • well my Element has an equals method that checks for the 4 boolean values and if they're the same it returns true. The output i get ("they're equal") shows that the equal method should have worked.. :/ – tObi Apr 26 '13 at 14:00
  • equals has been implemented for sure. – Lokesh Apr 26 '13 at 14:01
  • 1
    @tobi `Set` works with `equals` and **`hashCode`** methods. Your `Element` class **must** override the `hashCode` method as well in order to make the `Set` work as expected. If this isn't reflected in my answer, please refresh the page. – Luiggi Mendoza Apr 26 '13 at 14:01
  • Yup, can see it now :). I agree hashcode seems to be missing. – Lokesh Apr 26 '13 at 14:03
  • 1
    ok so this might be the answer.. i'll try it out, thx. but I'm a little bit confused about the need of the hashCode method becaus the doc of the sets add method says: More formally, adds the specified element e to this set if this set contains no element e2 such that (e==null ? e2==null : e.equals(e2)). (nothing about hashCode) – tObi Apr 26 '13 at 14:07
  • 2
    @tobi Unless you override `hashCode()` along with `equals()`, you are violating the contracts of those methods. Just because the Javadoc for `HashSet#add()` doens't refer to `hashCode()` doesn't mean you can ignore the `equals()` and `hashCode()` contracts. – GriffeyDog Apr 26 '13 at 14:24
4

Your objects have different hashes so HashSet "puts" then in different "buckets".

Lazarus Lazaridis
  • 5,803
  • 2
  • 21
  • 35
  • I noted that the **hashcode** of duplicate object are the same as you said. But how to solve it about much loop? – K.Sopheak Oct 05 '16 at 05:04
4

If you have your own model classes you need to change some basic functions work like done in the below example.

Execution code :

HashSet<MyModel> models = new HashSet<MyModel>();

for (int i = 1; i < 5; i++)
    models.add(new MyModel(i + "", "Name :" + i + ""));

for (int i = 3; i < 5; i++)
    models.add(new MyModel(i + "", "Name :" + i + ""));

for (Object object : models)
    System.out.println(object);

Model Class :

/**
 * Created by Arun
 */
public static class MyModel {

    private String id = "";
    private String name = "";

    public MyModel(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return getId();
    }

    @Override
    public boolean equals(Object obj) {
        return !super.equals(obj);
    }

    public int hashCode() {
        return getId().hashCode();
    }

}

Hope this helps.

Arun
  • 273
  • 3
  • 13
2

Yes We Can implement it with the object of the classes which are not FINAL.

HashSet Checks for two methods hashCode() and equals() before adding any Object. First it checks for the method hashCode(),if it returns the hashcode which is same with any of the object in Set, then it checks for the equals method for that object,which internally compares the references for both objects i.e this.obj1==obj.If these are the same references in that case it returns true means it is a duplicate value. We can add duplicate non-final objects by overriding HashCode and equals method. In HashCode() you can return same hashcode in case of same parameters.

See example:

public class Product {
int i;
Product(int a)
{
    this.i=a;
}
@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + i;
    return result;
}
@Override
public boolean equals(Object obj) {
    /*if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Product other = (Product) obj;
    if (i != other.i)
        return false;
    return true;*/
    return true;
}
}
`

`
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
    Product p1=new Product(1);
    Product p2=new Product(1);
    Product p3=new Product(1);
    Set s=new HashSet();
    s.add(p1);
    s.add(p2);
    s.add(p3);
    System.out.println(s.size());
}
}

The output will be 1.

P.S:Without overriding these methods,output will be 3 since it will use their default behavior.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • Certainly the solution was given.I just told the logic How the elements are compared before adding into a Set.So You must have to override both the methods to create a duplicate entry. If their hashcode() returns different,there is no mean to override equals(). – Shailesh Modi Sep 09 '14 at 10:04