4

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.

Mureinik
  • 297,002
  • 52
  • 306
  • 350
vap78
  • 1,029
  • 2
  • 11
  • 26
  • You changed the hash, which was remembered by HashSet, hence it can't identify an object. – Dims Oct 06 '15 at 06:25
  • Possible duplicate of [HashSet contains problem with custom objects](http://stackoverflow.com/questions/5110376/hashset-contains-problem-with-custom-objects) – SpaceTrucker Oct 08 '15 at 08:44

4 Answers4

9

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);
}
Mureinik
  • 297,002
  • 52
  • 306
  • 350
3

HashSet and HashMap use both hashCode and equals methods to locate an object in their inner structure. hashCode is used to find a correct bucket and then equals is consulted to distinguish between different objects with the same hashCode, as the latter is not guaranteed to be unique. In almost any cases this is an extremely bad idea to modify object which serves as a key in a HashMap or is put into a HashSet. If those modifications change either hashCode or semantics of equals method, your object will not be found.

Nikem
  • 5,716
  • 3
  • 32
  • 59
2

This is by-design behavior.

HashSet uses hashes to identify objects it holds.

So, if you change an object after it was placed into collection, it may be unable to find it.

You should either hold immutable objects only, or make mutable only that part of an object, which is not affect the hash.

I think better is to use HashMap, which clearly separates mutable and immutable parts.

Dims
  • 47,675
  • 117
  • 331
  • 600
0

It is quite clear, you are changing toInsert.v1 after adding to the set, and due to DemoClass obtain hashCode from v1 and v2 attributes, it won't find changed hashCode for elementes.

malaguna
  • 4,183
  • 1
  • 17
  • 33