Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HashSet.contains(object) returns false for instance modified after insertion

According to the JavaDoc of java.util.HashSet.contains() the method does following

Returns true if this set contains the specified element. More formally, returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e)).

However this does not seem to work for following code:

public static void main(String[] args) {
    HashSet<DemoClass> set = new HashSet<DemoClass>();
    DemoClass toInsert = new DemoClass();
    toInsert.v1 = "test1";
    toInsert.v2 = "test2";
    set.add(toInsert);
    toInsert.v1 = null;

    DemoClass toCheck = new DemoClass();
    toCheck.v1 = null;
    toCheck.v2 = "test2";

    System.out.println(set.contains(toCheck));
    System.out.println(toCheck.equals(toInsert));
}

private static class DemoClass {
    String v1;
    String v2;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((v1 == null) ? 0 : v1.hashCode());
        result = prime * result + ((v2 == null) ? 0 : v2.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;
        DemoClass other = (DemoClass) obj;
        if (v1 == null) {
            if (other.v1 != null)
                return false;
        } else if (!v1.equals(other.v1))
            return false;
        if (v2 == null) {
            if (other.v2 != null)
                return false;
        } else if (!v2.equals(other.v2))
            return false;
        return true;
    }

}

prints out:

false

true

So although the equals method returns true, HashSet.contains() returns false.

I guess this is because I modified the toInsert instance AFTER it was added to the collection.

However this is in no way documented (or at least I wasn't able to find such). Also the documentation referenced above the equals method should be used but it does not seem so.

like image 459
vap78 Avatar asked Dec 09 '22 01:12

vap78


1 Answers

When an object is stored in a HashSet its put in a data structure that's easily (read: efficiently) searchable by the object's hashCode(). Modifying an object may change its hashCode() (depending on how you implemented it), but does not update its location in the HashSet, as the object has no way of knowing its contained in one.

There are a couple of things you can do here:

  1. Modify the implementation of hashCode() so it isn't affected by the field you're changing. Assuming this field is important to the object's state, and participates in the equals(Object) method, this is somewhat of a code smell, and should probably be avoided.

  2. Before modifying the object, remove it from the set, and then re-add it once you're done modifying it:


Set<DemoClass> mySet = ...;
DemoClass demo = ...;
boolean wasInSet = mySet.remove(demo);
demo.setV1("new v1");
demo.setV2("new v2");
if (wasInSet) {
    set.add(demo);
}
like image 98
Mureinik Avatar answered Feb 19 '23 09:02

Mureinik