JakeQ
JakeQ

Reputation: 21

Problem with altering property's value of an object that is inside a HashSet in JAVA

I'm studying java and facing a really weird problem, i think it's easier to explain this along with my code

So this is my class:

class Node
{
    private int val;

    public Node(int val)
    {
        this.val = val;
    }

    public int getVal()
    {
        return this.val;
    }

    public void setVal(int newVal)
    {
        this.val = newVal;
    }

    @Override
    public int hashCode() {
        return this.val;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("Inside equal of " + this.val);  
       //i expect it will print out when i use set.contains()
        if(this.hashCode() == ((Node)obj).hashCode())
        {
            return true;
        }
        return false;
    }
}

And this is my Main block using HashSet that working with class Node

public class Main
{
    public static void main(String[] args)
    {
        Set<Node> s = new HashSet<>();
        Node n1 = new Node(1);
        s.add(n1);
        /*as i know, when we add a object to the set, we actually add a refference to the origin                             memory area (the one that n1 points to) to the set*/
        
        if(s.contains(n1))
        {
            System.out.println("YES");
        }
        
        for(Node i : s)
        {
            i.setVal(5);  //i expect n1 to change too and it does
        }

        for(Node i : s)
        {
            System.out.println(i.getVal());
        }
        System.out.println(n1.getVal());   //n1 changes
        
        if(s.contains(n1))
        {
            System.out.println("Still here");
            //This is where i don't understand why this does not print ?
        }
        System.out.println();
    } 
}

This is my cmd

D:\Desktop\draft\JavaDraft>java Main
YES
5
5

I don't understand why set does not realize n1 still be in it and why the code inside function "equals" of class Node is not triggered because as i know, HashSet uses "equals" to get uniqueness of all of it's element right? Thank you in advance.

Upvotes: 0

Views: 110

Answers (3)

pebble unit
pebble unit

Reputation: 1224

That is because by modifying the value of the Node, you are also changing the hashcode (thanks to your hashcode override). This is generally not the case and should not be the case as you do not want the hashcode to be variable. It should be constant and unchanging.

The underlying code of HashSet uses a HashMap, and the internal methods that contains() uses will use the hashcode of the object to determine a bucket to store your node in. Thus, when your hashcode is set to the value 1, and you call add(), the node n1 is stored for example at bucket 1. When you changed the value to 5, the hashcode is now 5 and calling s.contains(n1) will try to check for your node in bucket 5 which is empty (null).

A correct way to proceed will be to delete your implemented hashcode override and let the default implementation do its job. See also:

import java.util.*;

public class Main {
    public static void main(String[] args)
    {
        Set<Node> s = new HashSet<>();
        Node n1 = new Node(1);
        System.out.println(n1);
        s.add(n1);
        /*as i know, when we add a object to the set, we actually add a refference to the origin                             memory area (the one that n1 points to) to the set*/

        if(s.contains(n1))
        {
            System.out.println("YES");
        }

        for(Node i : s)
        {
            i.setVal(5);  //i expect n1 to change too and it does
        }

        for(Node i : s)
        {
            System.out.println(i.getVal());
        }
        System.out.println(n1.getVal());   //n1 changes

        System.out.println(n1);
        if(s.contains(n1))
        {
            System.out.println("Still here");
            //This is where i don't understand why this does not print ?
        }
        System.out.println();
    }
}

class Node
{
    private int val;

    public Node(int val)
    {
        this.val = val;
    }

    public int getVal()
    {
        return this.val;
    }

    public void setVal(int newVal)
    {
        this.val = newVal;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("Inside equal of " + this.val);
        //i expect it will print out when i use set.contains()
        if(this.hashCode() == ((Node)obj).hashCode())
        {
            return true;
        }
        return false;
    }
}

Output:

Without Hashcode override

Node@4783da3f
YES
5
5
Node@4783da3f
Still here

With Hashcode override

Node@1
YES
5
5
Node@5

Upvotes: 1

gusto2
gusto2

Reputation: 12085

HashSet uses "equals" to get uniqueness of all of it's element right?

Nope

HashSet is an facade for a HashMap, where the items are stored in the key set. The HashMap creates and compares the key hashCode from it's internal hash nodes.

Once the item is stored under its hashCode array element, it is there. Changing the hashCode will not replace it. What you wrote is a bad example how not-to-use the hashCode in the HashMap.

The contains method will check the key (hash array) of the underlying hashmap key set, not calling the equals method of all stored objects.

Upvotes: 1

philosophiagiotto
philosophiagiotto

Reputation: 1

You need to understand the difference between pass-by-reference and pass-by-value. Your override of the hashcode actually takes effect. However, you did not create a new object, the object in n1 and s is actually the same one.

Upvotes: -1

Related Questions