3

I have a class that contains a collection. The two instances of the class are equal if the contents of the collection are equal. While I am building my data structure I store the class in a HashSet, and the contents of the collection change. The changes cause a change in the hash code value. This seems to cause side effects where my data is lost in the Set. Removing the collection from the hashcode calculation fixes the problem, but violates rule where all fields in equals should be used in the hashcode.

How would you implement the hashcode in this situation?

public class LeveZeroHolder
{
private final Set<LevelOneHolder> orgGroups = new HashSet<LevelOneHolder>();
private final String name;

public LeveZeroHolder(String name, LevelOneHolder og)
{
    this.name = name;
    orgGroups.add(og);
    og.setFA(this);
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    if (obj == null || obj.getClass () != getClass ())
        return false;

    LeveZeroHolder hobj = (LeveZeroHolder)obj;
    return getOrgGroups().equals(hobj.getOrgGroups()) && getName().equals(hobj.getName());
}

@Override
public int hashCode()
{
    int rs = 17;
    rs = rs * 37 + ((getName() == null) ? 0 : getName().hashCode ());
    rs = rs * 37 + ((getOrgGroups() == null) ? 0 : getOrgGroups().hashCode());
    return rs;
}

public String getName()
{
    return name;
}

public Set<LevelOneHolder> getOrgGroups()
{
    return orgGroups;
}

public void addOrgGroup(LevelOneHolder o)
{
    o.setFA(this);
    orgGroups.add(o);
}
}
Aaron
  • 874
  • 3
  • 17
  • 34
  • Could you explain your model a little more? What is a LevelZeroHolder and a LevelOneHolder? The hashCode should ideally not change throughout the life of the object so i'd like to understand why this collection is in your hashCode in the first place. Does it need to be? – alpian Mar 07 '12 at 17:21
  • 3
    What do you mean by "This seems to cause side effects where my data is lost. "? – assylias Mar 07 '12 at 17:22
  • The code looks correct to me. I can't see anything that would cause data loss when adding an object. – John Haager Mar 07 '12 at 17:25
  • @JohnHaager since the OP returns the set of orgGroups and allows it to be changed, the hashCode will change, which won't play nicely with any Sets or Maps that this object is added to (they may appear to no longer be present). I presume this is what the OP is referring to. – alpian Mar 07 '12 at 17:29
  • @assylias I have A HashSet of LevelOneHolders and after parsing thousands of lines of data into this, I don't have as many levelOneHolders as expected. I am working on a small set of data to show the problem. – Aaron Mar 07 '12 at 18:52

3 Answers3

5

If you mean that when you store such objects as a key in a Map or in a Set they get lost, you might want to have a look at this thread which explains why storing mutable objects in a set in not a good idea, especially if they change while being held by the set.

Extract from the Set javadoc:

Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set. A special case of this prohibition is that it is not permissible for a set to contain itself as an element.

Community
  • 1
  • 1
assylias
  • 321,522
  • 82
  • 660
  • 783
  • Basically, you can't allow anyone to modify the collection after you've created the object, and you're going to have to redesign your code to compensate for that. Use `Collections.unmodifiableXXX` or Guava's `ImmutableXXX` to enforce unmodifiability. – Louis Wasserman Mar 07 '12 at 18:41
  • Since I like playing with fire, and don't have time to fully redesign, I chose to remove the object about to be modified and re add after the modification. – Aaron Mar 07 '12 at 21:26
0

If your collection of LevelOneHolder really does affect the uniqueness of your object then you should make LevelZeroHolder immutable. If you add a LevelOneHolder to your LevelZeroHolder then you should, instead of updating the collection, return a completely new LevelZeroHolder with the collection copied from the pre-existing one and joined with the new one you want to add.

In this way the hashCode never changes but you end up with different LevelZeroHolders with different hashCodes. This is probably correct though considering that you're saying that the nested collection within LevelZeroHolder contributes to the uniqueness of that object.

alpian
  • 4,668
  • 1
  • 18
  • 19
0

If you put your object in a data structure where you depend on the hashCode or the equals() to find it, AND if you change some property of the object (like its name, or something in the list in it) when it is already in that data structure, then you wont find it. Is that your case?

The reason is that when you put the object in such a data structure, it is inserted according to its hashCode, at the time of insertion. If when its inside, you change something that changes the hashCode, then your object is still stored according to the old hashCode but when you try to get it, and so you compute its hashCode to see if thats your object, then you get the new hashCode value and so you dont see that its the one you want.

The rule is that for an object in a data structure that uses the object's hash code or equals NEVER change the object in a way that affects the hashCode or equals.

Try using immutable objects instead.

baba smith
  • 711
  • 7
  • 16