2

I overrided the hashCode and equals method of a class. I also wrote unit tests for it and it's all green. When I tested it out with a hashmap, I noticed something weird. I created 2 identical objects:

obj1 = new PacmanVeld(field2);
obj2 = new PacmanVeld(field2);

I tested it out with this piece of code:

Assert.assertTrue(obj1.hashCode() == obj2.hashCode()); //works
Assert.assertTrue(obj1.equals(obj2)); //works

HashMap<PacmanVeld, Integer> testMap = new HashMap<>();
testMap.put(obj1, 5);

Assert.assertTrue(testMap.put(obj2, 7) == 5); //fails throws nullpointerexception
Assert.assertTrue(testMap.get(obj1) == 7); //fails 

I don't understand why this won't work as I understand that in the algorithms of a HashMap, obj1 and obj2 are the same objects.

The PacmanVeld class:

public class PacmanVeld
{
    private Node[][] nodes;

    public PacmanVeld(char[][] veld)
    {
        this.nodes = new Node[veld.length][veld[0].length];

        for (int i = 0; i < veld.length; i++)
        {
            for (int j = 0; j < veld[i].length; j++)
            {
                switch (veld[i][j])
                {
                    case '%':
                        nodes[i][j] = new Node(i, j, NodeType.WALL);
                        break;
                    case ' ':
                        nodes[i][j] = new Node(i, j, NodeType.EMPTY);
                        break;
                    case '.':
                        nodes[i][j] = new Node(i, j, NodeType.CRUMB);
                        break;
                    case 'P':
                        nodes[i][j] = new Node(i, j, NodeType.PACMAN);
                        break;
                }
            }
        }
        initFinish();
        initPacman();
    }

    //getters, setters & methods

    public boolean equals(PacmanVeld p)
    {
        if (p.nodes.length != nodes.length) { return false; }
        for (int i = 0; i < nodes.length; i++)
        {
            if (!Arrays.deepEquals(nodes[i], p.nodes[i])) { return false; }
        }
        return true;
    }

    @Override
    public int hashCode()
    {
        List<Node> nodeList = getNodeList();
        return Arrays.deepHashCode(nodeList.toArray());
    }

    private void initPacman()
    {
        for (Node[] rij : this.nodes)
        {
            for (Node n : rij)
            {
                if (n.isPacman())
                {
                    pacman = n;
                }
            }
        }
    }
}
3
  • 2
    Could you show your PacmanVeld class ? Commented Nov 30, 2013 at 16:46
  • And include the return from your two testMap.put assertions calls. Commented Nov 30, 2013 at 16:50
  • How are you getting a NPE on testMap.put(obj2, 7) == 5? put can't throw an exception according to the docs. What value is null exactly? Commented Nov 30, 2013 at 17:00

3 Answers 3

3

The problem is that you didn't override the equals() method herited from the object class.

@Override
public boolean equals(Object o)
    {
        PacmanVeld p = (PacmanVeld)o;
        if (p.nodes.length != nodes.length) { return false; }
        for (int i = 0; i < nodes.length; i++)
        {
            if (!Arrays.deepEquals(nodes[i], p.nodes[i])) { return false; }
        }
        return true;
    }
Sign up to request clarification or add additional context in comments.

1 Comment

As we thought: wrong HashCode or Equals implementation :)
0

I wasn't able to reproduce your error. Since it is too long to post as a comment, I will add it as an answer and edit my post to explain your mistake once you have showed your HashCode and Equals implementation.

public class Test {
    public static void main(String[] args) {
        Map<MyPersonalClass, Integer> map = new HashMap<>();
        MyPersonalClass obj1 = new MyPersonalClass();
        obj1.someInt = 5;
        obj1.someString = "test";

        MyPersonalClass obj2 = new MyPersonalClass();
        obj2.someInt = 5;
        obj2.someString = "test";

        System.out.println(obj1.equals(obj2));
        System.out.println(obj1.hashCode() == obj2.hashCode());

        map.put(obj1, 5);
        System.out.println(Arrays.toString(map.values().toArray()));

        System.out.println(map.put(obj2, 10) == 5);
        System.out.println(Arrays.toString(map.values().toArray()));
        System.out.println(map.get(obj1));
    }
}

class MyPersonalClass {
    public String someString;
    public int someInt;

    @Override
    public boolean equals(Object arg0) {
        if(arg0 == this) { return true; }
        if(!(arg0 instanceof MyPersonalClass)) { return false; }

        MyPersonalClass obj = (MyPersonalClass) arg0;

        return obj.someString.equals(this.someString) && obj.someInt == this.someInt;
    }

    @Override
    public int hashCode() {
        return this.someString.hashCode() * 37 + this.someInt;
    }
}

Output:

true
true
[5]
true
[10]
10

1 Comment

@Masud: that's the entire idea behind the question. Quote from the question "I created 2 identical objects" and Assert.assertTrue(obj1.equals(obj2)); //works
0

HashMap return null for new value. Your Assert.assertTrue(testMap.put(obj2, 7) == 5); test case throws NPE exception because, obj2 is absent in HashMap. You can test with obj1 which already put on HashMap

  Assert.assertTrue(testMap.put(obj2, 7) == 5); 
                  //throws NPE because obj2 is not inserted

Try, with obj1

  Assert.assertTrue(testMap.put(obj1, 7) == 5); 
          //will retrun true because, 5 is last inserted value with obj1
  Assert.assertTrue(testMap.put(obj1, 7) == 8); 
         // will retrun false because

9 Comments

Yes of course, that's true. But The first assertion throws a nullpointer exception so that's not the only problem I think...
I don't believe this to be the issue. This works perfectly fine: Map<String, Integer> map = new HashMap<>(); map.put("test", 7); System.out.println(map.put("test", 9) == 7);
@AmirAfghani: Integer caches until 127. It still works with 700 & 999.
@AmirAfghani, The comparison with two Integer reference might be an issue if any one of them has value higher than 127. But comparison with direct constant expression(plain integer) will not be, because it will automatically unbox the Integerreference to int before the comparison. Masud's answer is wrong
I strongly doubt your edit is the source of the issue as well. The entire idea behind the question is that obj1 and obj2 are the same and thus it shouldn't matter which one is put in. I suspect a wrongly implemented Equals/Hashcode instead.
|

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.