arpansen
arpansen

Reputation: 205

HashSet adding equivalent mutable objects

I know I should have made the Public Class immutable but still I already changed the name of the a Person object before it was added to the Set set of type HashSet and my Person class also implements the hashCode() and equals() methods which help to check weather an object is same as the previous object or not and prevent addition to the list if it is equivalent with the help of the hashCode() and the equals() methods, but still the output is : bob charlie bob

instead of: bob charlie

If i replace the code piece of the Main Class:

Set<Person> set = new HashSet<Person>();
        Person a= new Person("alice",45);
        Person b=new Person("bob",41);
        Person c= new Person("charlie",48);
        set.add(a);
        a.name="bob";
        set.add(b);
        set.add(c);

By the following code down below (i.e. I declared an equivalent object directly without changing the name later as in the original problematic code):

Set<Person> set = new HashSet<Person>();
        Person a= new Person("bob",45);
        Person b=new Person("bob",41);
        Person c= new Person("charlie",48);
        set.add(a);
        set.add(b);
        set.add(c);

Then the object b is not added.

And also another point to note is that I changed the name of object(in the original problematic code) a before the addition of object b but still it is adding b why????

My original problematic code which contains the Main Class followed by the Public Class is given in full detail below::

//Main.java class of generics_practice_test package;
package generics_practice_test;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
public class Main {


    public static void main (String args[])
    {
        Set<Person> set = new HashSet<Person>();
        Person a= new Person("alice",45);
        Person b=new Person("bob",41);
        Person c= new Person("charlie",48);
        set.add(a);
        a.name="bob";
        set.add(b);
        set.add(c);


        for(Iterator<Person> iterator=set.iterator();iterator.hasNext();){

            System.out.println(iterator.next());

        }



    }


}

And the code for the Person Class is below: //Person class of generics_practice_test package is below

package generics_practice_test;

public class Person implements Comparable<Person> {

    public String name;

    public int age;

    public Person(String name,int age)
    {
        this.name=name;
        this.age=age;
    }

    public String toString()
    {
        return this.name;
    }

    public int compareTo(Person o)
    {
        int myLength=name.length();
        int oLength=o.name.length();
        if(myLength == oLength) return 0;
        if(oLength > myLength) return -1;
        return 1;

    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

}

Upvotes: 0

Views: 2005

Answers (3)

bitkot
bitkot

Reputation: 4504

HashSet internally uses HashMap and HashMap is an implementation of hashtable data-structure.

Hashtables uses has a concept of storing data in buckets.

Think of it like a map, where key is the hashCode and the value a list of elements.

Now what is happening here is that at the time of storing, it gets the hashCode() and stores the current element wherever it fit possible.

In you case it is name hashCode. Now takes for an example your bob's hash value is 32 so there will be an entry with 32 as key and list will contain the element.

You change the name which does not really change the position of the element.

While inserting HashSet follows the below steps.

  • Get the hashCode value of the object
  • Search the bucket with that value.
  • if the bucket is found then iterate through the list and call equals on all of them with current object.
  • if object is found then don't insert it, otherwise insert it.

Upvotes: 0

Luiggi Mendoza
Luiggi Mendoza

Reputation: 85779

A HashSet will store the elements based on the result of hashCode method. This is a pseudo algorithm used in the implementation:

  • Retrieve the object hashCode. Store it in int possibleIndex.
  • Obtain the index where the object could be stored based on possibleIndex. Store it in index.
  • Retrieve a List stored in index. Traverse this list and seek if the object is not inside this list by using equals method. This is do in order to handle collisions, or objects with the same hashCode.

Note that for your example, Person#hashCode implementation relies on the name element only.

Let's review your first piece of code:

//create the new HashSet
Set<Person> set = new HashSet<Person>();
//create your elements to be inserted
Person a= new Person("alice",45);
Person b= new Person("bob",41);
Person c= new Person("charlie",48);
//try to add "a". It will calculate hashCode from name field,
//which value is "alice"
set.add(a);
//you change the name here, but it won't affect the previous
//operation because the index for "a" was calculated using "alice",
//not "bob" and IT WONT BE RECALCULATED!
a.name="bob";
//try to add "b". It will calculate hashCode from name field,
//which value is "bob"
//as noted before, there's no index based on "bob"'s hashCode,
//so it will be added with no problems
set.add(b);
//try to add "c". It will calculate hashCode from name field,
//which value is "charlie"
set.add(c);

So, all the elements are inserted with no problems because there's no collision of hashCode (that's why previous explanation doesn't cover the call to equals method).

Now, let's review your second piece of code:

//create the new HashSet
Set<Person> set = new HashSet<Person>();
//create your elements to be inserted
Person a= new Person("bob",45);
Person b=new Person("bob",41);
Person c= new Person("charlie",48);
//try to add "a". It will calculate hashCode from name field,
//which value is "bob"
set.add(a);
//try to add "b". It will calculate hashCode from name field,
//which value is "bob"
//since the index calculated from the hashCode of "bob" is
//already inserted, it will check if the element already exists
//Looking at Person#equals, which is based on name field only
//there is an element where the name field has a value of "bob"
//"b" won't be inserted
set.add(b);
//try to add "c". It will calculate hashCode from name field,
//which value is "charlie"
set.add(c);

Upvotes: 3

Lena Bru
Lena Bru

Reputation: 13947

try changing your equals method to this

 @Override
    public boolean equals(Object obj) {

    if (obj == null) //first you check if object is null
        return false;
    if (getClass() != obj.getClass()) // then you check if object is of the same class
        return false;
    Person other = (Person) obj;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

the statement if (this == obj) checks the objects by address in memory. Address in memory of every object will be different! if you used new on the object

Upvotes: -1

Related Questions