Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java HashMap with overridden hashCode() and equals() returns no data

Tags:

java

hashmap

I have been working with Java's HashMap lately and have run afoul of some interesting behavior. I am currently using it to store key / value objects with multiple fields. To do this I have overridden hashCode() and equals() as follows:

public final class TransitionState {

private String mStackSymbol;
private String mTransitionSymbol;
private int mState;

private static final int HASH_SEED = 7;     //Should be prime
private static final int HASH_OFFSET = 31;

//Constructor and getter methods here

public boolean equals(TransitionState other) {

    //Check that we aren't comparing against ourself
    if (this == other) {
        return true;
    }

    //Check that we are not comparing against null
    if (other == null) {
        return false;
    }

    //Check fields match
    if ((mState == other.getState()) &&
        (mTransitionSymbol.equals(other.getTransitionSymbol())) &&
        (mStackSymbol.equals(other.getStackSymbol()))) {

        return true;

    } else {
        return false;

    }
}

public int hashCode() {
    int intHash = HASH_SEED;

    //Sum hash codes for individual fields for final hash code
    intHash = (intHash * HASH_OFFSET) + mState;
    intHash = (intHash * HASH_OFFSET) + (mTransitionSymbol == null ? 0 : mTransitionSymbol.hashCode());
    intHash = (intHash * HASH_OFFSET) + (mStackSymbol == null ? 0 : mStackSymbol.hashCode());

    return intHash;
}
}

Now, I am able to put items in the Map without issue. Retrieving them, however, is another story. Whenever I attempt to get() from the HashMap, NULL is returned. I wrote some test code to iterate over the Map and print values, which is where things get confusing as the hashCode() of my key objects matches that which I have in my map and equality with a known value returns true. Sample output as follows (see fourth transition from bottom of table):

Transition Table:
State  Symbol  Stack  Move
--------------------------
1, a, b, (1, pop) with key hashcode 212603 and value hashcode 117943
0, b, a, (0, pop) with key hashcode 211672 and value hashcode 117912
1, b, z, (1, push) with key hashcode 212658 and value hashcode 3459456
0, a, b, (0, pop) with key hashcode 211642 and value hashcode 117912
1, a, z, (0, push) with key hashcode 212627 and value hashcode 3459425
0, a, a, (0, push) with key hashcode 211641 and value hashcode 3459425
0, a, z, (0, push) with key hashcode 211666 and value hashcode 3459425
0, b, z, (1, push) with key hashcode 211697 and value hashcode 3459456
1, b, a, (1, pop) with key hashcode 212633 and value hashcode 117943
1, b, b, (1, push) with key hashcode 212634 and value hashcode 3459456

ababba

Transition from (0, a, z) with hashcode 211666
transition.equals(new TransitionState(0, "a", "z")) = true
HashMap containsKey() = false
Transition not found
false

As you can see, the key matches hashcodes with an entry in the map, but I am being told that is does not exist. I tried debugging into the HashMap's containsKey() method which does a get() which is checked for NULL. Stepping into the get() shows the the loop there is only being run once before returning NULL.

So, is this a HashMap problem (likely not) or (more likely) what could I be doing wrong? Thank you in advance for your help.

like image 895
MysteryMoose Avatar asked Nov 24 '10 17:11

MysteryMoose


2 Answers

You haven't overridden equals properly... you need

 public boolean equals(Object other)

Currently you're just overloading it.

Basically the real override could still do the same job as the existing one, but with a test first:

if (!(other instanceof TransitionState))
{
    return false;
}
TransitionState otherState = (TransitionState) other;
// Now do the rest of the comparison

Note that you don't need the check for null in this case, as it would fail the instanceof test.

These days Java allows you to add an annotation to tell the compiler you're really trying to override a parent method:

 @Overrides
 public boolean equals(Object other)

Now the compiler will tell you if you make a typo in the name or get the signature wrong.

like image 177
Jon Skeet Avatar answered Nov 14 '22 22:11

Jon Skeet


Jon is correct. Additionally you should do the following:

Make your instance variables final:

private final String mStackSymbol;
private final String mTransitionSymbol;
private final int mState;

If you cannot do that then make sure you are not changing the values of those variables after you put the items into the map. If the state changes after you put them into the map you won't be able to get them out again (at least not by their original values).

like image 41
TofuBeer Avatar answered Nov 14 '22 22:11

TofuBeer