1

I've written a hashcode function for this class and for some reason the hashmap is not properly recognizing when a key is actually present in the HashMap. (coordinates is a string)

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

And I've also written a .equals method for the class that used to test whether two pairs of coordinates were equal to each other; However, to verify that my hashcode method is working correctly I've switched the .equals method to the following.

public boolean equals(Object arg) {
    Block a = (Block) arg;
    return hashCode() == a.hashCode();
}

and they're all being called elsewhere with a hashmap.containskey() call as follows:

return (hashblocks.containsKey(newz));

For some reason this only returns true ~ 50% of the time when it should (we even reinput the exact same case and sometimes it works sometimes it doesn't) I've had a lot of problem in the past trying to get contains methods to work properly for HashMaps and Sets and I'm wondering why this implementation in particular is having difficulty. (Basically, what could the bug possibly be)

3 1 3 1
true
4 2 4 2
false
 0 0 1 0
 0 3 1 3
 2 0 3 0
 2 3 3 3
0 1 1 2
2 1 2 2
4 0 4 0
4 2 4 2
3 1 3 1
3 2 3 2

3 1 3 1
true
4 2 4 2
true
3 2 3 2
true
 0 0 1 0
 0 3 1 3
 2 0 3 0
 3 3 4 3
0 1 1 2
2 1 2 2
4 0 4 0
4 2 4 2
3 1 3 1
3 2 3 2

the query is followed by its result and the long set of numbers represents all the keys followed by a newline char

Raul Puri
  • 119
  • 1
  • 1
  • 12

2 Answers2

2

to verify that my hashcode method is working correctly I've switched the .equals method to the following.

return hashCode()==a.hashCode();

This is going to work only in case of "perfect hashing", i.e. when equality of hash codes implies the equality of the actual values. Hash codes of String in Java are not perfect (in fact, they cannot be perfect for all possible strings even theoretically).

You need to keep the equality check consistent with hash codes - in your case, that would be checking the equality of coordinates:

public boolean equals(Object arg) {
    Block a = (Block) arg;
    return coordinates.equals(a.coordinates);
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • what you suggested is the initial setup I had; however, that did not function appropriately. I've even switched the hashcode functions to 'public int hashCode(){ String[] ints=coordinates.split(" "); return Integer.parseInt(ints[0])*1+Integer.parseInt(ints[1])*2+Integer.parseInt(ints[2])*3+Integer.parseInt(ints[3])*4; }' to circumvent this problem of imperfect hashing however I still receive the same error. – Raul Puri Aug 16 '14 at 12:41
  • @RaulPuri Is the `coordinates` string changeable, or is it set once in the constructor, and cannot be changed again? – Sergey Kalinichenko Aug 16 '14 at 12:43
  • It is a private variable, but it is intended to be changed as you can move the bloc's coordinates around could this be the problem? – Raul Puri Aug 16 '14 at 12:50
  • I encountered a null pointer error occasionally earlier in my code when I tried to move the block around but that fixed itself – Raul Puri Aug 16 '14 at 12:50
  • 1
    @RaulPuri Basing hash code and equality on mutable fields is dangerous. Here is what could happen: (1) put a block into a hash container, (2) change its coordinates, (3) iterate over the existing blocks from the container. Most blocks whose coordinates have been changed after storing in the container would not be located, because the hash code used to place them is no longer consistent with their new hash code. – Sergey Kalinichenko Aug 16 '14 at 12:54
  • Hmmm that's an interesting problem to have. could I maybe ask you to glance at the testcases that I edited into my file. Even though the key is present in the hashmap the function is not returning the appropriate result. Also I've changed my hashcode function so it parses in all the ints from each line and multiplies each integer by a unique factor to get a final hashcode. I thought this might be able to fix the problem, but clearly I was wrong – Raul Puri Aug 16 '14 at 12:59
  • actually you're entirely right the error seems to be in the mutability of the hashcode and I can't seem to comprehend why. My understanding is that it should be basing its hashcode off of whatever the coordinates are – Raul Puri Aug 16 '14 at 13:02
  • @RaulPuri When you place a key in a hashmap, the hashmap computes the hash code of the key. Let's say it returns 1. The hashmap places the key in a bucket number 1, after checking other items in that bucket for equality to the new key. Then you change the key, so that its new hash code becomes 7. However, the map does not know about it, so the key remains in bucket 1. When you search the map using another key with hash code 7, hashmap checks bucket number 7, and does not find our object in there, because it remains in the bucket number 1. – Sergey Kalinichenko Aug 16 '14 at 13:12
2

Since coordinates seems mutable, you should not use it as hashCode(), because you will encounter problem with hash based container, eg:

Map<Object,Object> map = new HashMap<>();
key.setCoordinates("1");
map.put(key, value1); 
key.setCoordinates("2");
map.put(key, value2); 

The key remains the same, but its hashCode is based on coordinates which is mutable. In the first case, when coordinates == "1", its value might be 1. Since the Hash map/set use an internal array, of a certain capacity (say 16), it will store the value1 at the following position:

map.values[(key.hashCode() % map.values.length)] = value1;
map.values[(1 % 16)] = value1;
map.values[1] = value1;

In fact, the array is an array of list (eg: two keys may have the same hash code, and that's where the equals method is used) but I don't want to go down the full implementation of HashMap/Set in Java.

If the hashCode mutate, then calling put a second times will not work: say that "2".hashCode() is 2, and that key.hashCode() also returns 2:

map.values[(key.hashCode() % map.values.length)] = value2;
map.values[(2 % 16)] = value2;
map.values[2] = value2;

But for the same key, you already have a value associated, yet it is not replaced by value2. I can even assume that System.out.println(map); will print [key: value1, key: value2] in a random order.

That why an hashCode method should avoid using mutating field, unless you are sure that when you use your map, the keys never mutate.

You should probably return 0, or use the default hashCode()/equals().

The same problem may arise with equals.

NoDataFound
  • 11,381
  • 33
  • 59