HashMap Remove is not working

android, hashmap, java

Solution

Let's say an Item is added to a HashMap with size 16. It has hashCode=20. Android's HashMap creates an entry in bucket 4, storing the actual hash (20) and a reference to the Item itself.

Now let's say the Item is modified so that the hashCode is changed to 36.

If we now run containsKey() using the same Item reference, Android's HashMap will look in the same bucket, 4. There it finds the key based on identity (==). It's the same object, but at this point it doesn't care about the hash code.

If we however run remove() using the same Item reference, Android's HashMap again looks in bucket 4. However this code does not perform the identity (==) check - it checks the computed hashCode and equals(). But since the computed hashCode has changed (20 vs 36), removal fails.

So, to sum up: The hashCode has changed since the object was added and the new hash code happens to map to the same bucket.

(Note that I find this bucket collision unlikely myself, especially given that the implementation does some magic bit operations to protect from poor hashing algorithms. There may be other explanations, but this is the only one I find logical based on findings so far.)

Problem

I've read all answers on this issue and nothing seems to be working. Here's the code I'm running: ``` HashMap<TicketItem, ArrayList<TicketItemModifier>> items = new HashMap<TicketItem, ArrayList<TicketItemModifier>>(); for (final TicketItem key : items.keySet()) { Log.i(key.equals(item)); Log.i(key.hashCode() + " " + item.hashCode()); } Log.i(items.size()); Log.i("C: " + items.containsKey(item)); items.remove(item); Log.i("C: " + items.containsKey(item)); Log.i(items.size()); for (final TicketItem key : items.keySet()) { Log.i(key.equals(item)); Log.i(key.hashCode() + " " + item.hashCode()); } ``` I can't provide working code because it's really a huge project. I've gone ahead and printed everything out (as you can see in my example code) so you know the values being used by the HashMap are correct. Log: ``` 03-22 18:58:10.125: I/POSDoes(29790): true 03-22 18:58:10.125: I/POSDoes(29790): -823765791 -823765791 03-22 18:58:10.125: I/POSDoes(29790): false 03-22 18:58:10.125: I/POSDoes(29790): 1543283745 -823765791 03-22 18:58:10.125: I/POSDoes(29790): false 03-22 18:58:10.125: I/POSDoes(29790): 427224321 -823765791 03-22 18:58:10.125: I/POSDoes(29790): false 03-22 18:58:10.125: I/POSDoes(29790): -616760351 -823765791 03-22 18:58:10.125: I/POSDoes(29790): 4 03-22 18:58:10.125: I/POSDoes(29790): C: true 03-22 18:58:10.130: I/POSDoes(29790): C: true 03-22 18:58:10.130: I/POSDoes(29790): 4 03-22 18:58:10.130: I/POSDoes(29790): true 03-22 18:58:10.130: I/POSDoes(29790): -823765791 -823765791 03-22 18:58:10.130: I/POSDoes(29790): false 03-22 18:58:10.130: I/POSDoes(29790): 1543283745 -823765791 03-22 18:58:10.130: I/POSDoes(29790): false 03-22 18:58:10.130: I/POSDoes(29790): 427224321 -823765791 03-22 18:58:10.130: I/POSDoes(29790): false 03-22 18:58:10.130: I/POSDoes(29790): -616760351 -823765791 ``` As you can see, the hash codes match, and the equals method returns true, but when printing out contains, I'm receiving true before and AFTER I call the remove method. Why doesn't this work? Update I've printed out item.equals(key) with the same result. ``` for (final TicketItem key : items.keySet()) { Log.i(item.equals(key)); Log.i(key.equals(item)); Log.i(key.hashCode() + " " + item.hashCode()); } Log.i(items.size()); Log.i("C: " + items.containsKey(item)); items.remove(item); Log.i("C: " + items.containsKey(item)); Log.i(items.size()); for (final TicketItem key : items.keySet()) { Log.i(item.equals(key)); Log.i(key.equals(item)); Log.i(key.hashCode() + " " + item.hashCode()); } ``` Log: ``` 03-22 19:09:14.360: I/POSDoes(30458): true 03-22 19:09:14.360: I/POSDoes(30458): true 03-22 19:09:14.360: I/POSDoes(30458): 1543283745 1543283745 03-22 19:09:14.365: I/POSDoes(30458): false 03-22 19:09:14.365: I/POSDoes(30458): false 03-22 19:09:14.365: I/POSDoes(30458): 427224321 1543283745 03-22 19:09:14.365: I/POSDoes(30458): false 03-22 19:09:14.365: I/POSDoes(30458): false 03-22 19:09:14.365: I/POSDoes(30458): -616760351 1543283745 03-22 19:09:14.365: I/POSDoes(30458): 3 03-22 19:09:14.365: I/POSDoes(30458): C: true 03-22 19:09:14.365: I/POSDoes(30458): C: true 03-22 19:09:14.365: I/POSDoes(30458): 3 03-22 19:09:14.365: I/POSDoes(30458): true 03-22 19:09:14.365: I/POSDoes(30458): true 03-22 19:09:14.365: I/POSDoes(30458): 1543283745 1543283745 03-22 19:09:14.365: I/POSDoes(30458): false 03-22 19:09:14.365: I/POSDoes(30458): false 03-22 19:09:14.365: I/POSDoes(30458): 427224321 1543283745 03-22 19:09:14.370: I/POSDoes(30458): false 03-22 19:09:14.370: I/POSDoes(30458): false 03-22 19:09:14.370: I/POSDoes(30458): -616760351 1543283745 ``` Update 2 Here is the implementation for equals() ``` @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (this.getClass() != obj.getClass()) return false; TicketItem other = (TicketItem) obj; if (!getValues().equals(other.getValues())) return false; return true; } ``` getValues() returns a LinkedHashMap with arbitrary (but equal!) values. Update 3 The equals method is passing correctly (no issues). However it seems that the hash code is changing. When I call the remove function, all hashes saved inside the hash map return equal. The hash code being generated is actually just a different LinkedHashMap's hash code. Meaning the hashCode() function of TicketItem uses the hashCode() function of LinkedHashMap.

Original source