-2

When I edit an object, which is contained within a HashSet, the hash of the object changes, but the HashSet is not updated internally. Therefor, I practically can add the same object twice:

TestObject testObject = new TestObject(1, "hello");
Set<TestObject> set = new HashSet<>();
set.add(testObject);
testObject.number = 2;
set.add(testObject);
set.forEach(System.out::println);
//will print
//{number:2, string:hello}
//{number:2, string:hello}

Full working code example:

import java.util.*;

public class Main {

  public static void main(String[] args) {
    TestObject testObject = new TestObject(1, "hello");
    Set<TestObject> set = new HashSet<>();

     // add initial object
    set.add(testObject);

    // modify object
    testObject.number = 2;
    testObject.string = "Bye";

    // re-add same object
    set.add(testObject);
    set.forEach(System.out::println);
  }
}

class TestObject {

  public int number;
  public String string;

  public TestObject(int number, String string) {
    this.number = number;
    this.string = string;
  }

  @Override
  public int hashCode() {
    return Objects.hash(number, string);
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof TestObject)) {
      return false;
    }
    TestObject o = (TestObject) obj;
    return number == o.number && string.equals(o.string);
  }

  @Override
  public String toString() {
    return "{number:" + number + ", string:" + string + "}";
  }
}

This means, after modifying an object which already is contained in a HashSet, theHashSet` turns unreliable or invalid.

Modifying an object that is somewhere contained in a Set (probably even without knowing) seems a regular use case to me . And something which I probably already have done a lot.

This throws me back and brings one basic question to me: When or why should I use a HashSet if it has such a behaviour?

Herr Derb
  • 4,977
  • 5
  • 34
  • 62
  • 2
    Your issue is in `equals()`: `string == o.string` – Zircon Oct 16 '19 at 13:50
  • 5
    Possible duplicate of [How do I compare strings in Java?](https://stackoverflow.com/questions/513832/how-do-i-compare-strings-in-java) – Sean Bright Oct 16 '19 at 13:51
  • 4
    But more generally, changing an object *in a way which affects equals and hashCode* means you won't be able to find it in a hash-based map or set. This has been true forever in both Java and .NET, and I can't remember the last time I found it to be a problem. If you modify an object in a way which doesn't affect equality, that's fine. – Jon Skeet Oct 16 '19 at 13:54
  • You can put two unequal things into a set. If you then change one of them to be equal to the other, how would you expect the set to handle that? – Andy Turner Oct 16 '19 at 14:09
  • @Unimportant Yes that's correct. Changed to `equals` the case still keeps valid. – Herr Derb Oct 18 '19 at 11:46

3 Answers3

2

Well, if you have a look at the HashSet source you'll see that it's basically a HashMap<E, Object> with the elements being the keys - and modifying keys of a hashmap is never a good idea. The map/set will not be updated if the hash would change, in fact the map/set wouldn't even know about that change.

In general keys of a HashMap or elements in a HashSet should be immutable in that their hash and equality doesn't change. In most cases the hash and equality are based on those object's (business) identity, so if number and string are both part of that object's identity then you shouldn't be able to change those.

Modifying an object that is somewhere contained in a Set (probably even without knowing) seems a regular use case to me . And something which I probably already have done a lot.

It's probably true that objects contained in sets are modified quite often but that normally would mean that data that's not used to generate the hashcode or to check equality are modified. As an example let's say a person's hashcode is based on their ID number. That would mean that hashCode() and equals() should only be based on that number and that everything else could be modified safely.

So you could modify elements in a HashSet as long as you're not modifying their "id".

When or why should I use a HashSet if it has such a behaviour?

If you need to store mutable objects in a HashSet you have a few options which basically revolve around using only the immutable parts for hashCode() and equals(). For sets that could be done by using a wrapper object that provides a customized implementation for those methods. Alternatively you could extract one or more immutable properties and use those as the key into a map (in case of multiple properties you'd need to build some sort of key object out of those)

Thomas
  • 87,414
  • 12
  • 119
  • 157
0

You’re never supposed to compare strings with == use .equals instead

0

Adding an element that is already present, as you said, won't override the element that is already in the HashSet. Use a remove(), before calling the add(), to insure the new value to be inserted effectively.

Side note: as some users have noted, pay attention to the Strings' comparisons in your test.

Andrea
  • 6,032
  • 2
  • 28
  • 55