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

3
  • 1
    Please show us a minimal test-case. Commented Aug 16, 2014 at 12:32
  • @OliCharlesworth I've just edited the post to include a failed testcase Commented Aug 16, 2014 at 12:44
  • That's not how you write an equals method. Commented Aug 16, 2014 at 13:14

2 Answers 2

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);
}
Sign up to request clarification or add additional context in comments.

8 Comments

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.
@RaulPuri Is the coordinates string changeable, or is it set once in the constructor, and cannot be changed again?
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?
I encountered a null pointer error occasionally earlier in my code when I tried to move the block around but that fixed itself
@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.
|
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.