6

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.

2 Answers 2

19

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.

Sign up to request clarification or add additional context in comments.

4 Comments

the perfect reason why @Overrides should always be used.
@matt b: I was about to add that :)
Good lord, you are correct. I was so focused on the implementation that the simplest thing got me. Worked like a charm. Thank you for the prompt response.
I cannot express how happy I am to have found this post. I'm working on a DFA for educational purposes, and didn't override equals(), and I was sitting there frustrated for the past hour wondering why. It works now... It works... I'm so happy that it works!!!
2

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).

1 Comment

I was thinking about that but I just opted to design the entire class such that it cannot be modified (no setters, all members private, etc). Though, I suspect that this would make the design of the class more readily apparent to someone down the line.

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.