I have a strange issue occuring in my application, I will quickly explain global architecture and then my problem in depth.
I use a service to populate a HashM
Is your DomainObject
class immutable? Does it have properly implemented hashCode
and equals
methods?
Note that you will get into trouble if your DomainObject
class is not immutable and you change the state of the object while it is in the map in a way that would change the result of calling hashCode
or equals
.
hashCode
must be implemented in such a way that it returns the same value for two objects whenever equals
returns true when comparing these objects. See the API documentation of java.lang.Object.hashCode()
for detailed information.
[This basically expands on Jesper's answer but the details may help you]
Since recreating the map using new HashMap(map)
is able to find the element I am suspecting that the hashCode()
of the DomainObject changed after adding it to the Map.
For example if your DomainObject looks the following
class DomainObject {
public String name;
long hashCode() { return name.hashCode(); }
boolean equals(Object other) { /* compare name in the two */'
}
Then
Map<DomainObject, Boolean> m = new HashMap<DomainObject, Boolean>();
DomainObject do = new DomainObject();
do.name = "ABC";
m.put(do, true); // do goes in the map with hashCode of ABC
do.name = "DEF";
m.get(do);
The last statement above will return null
. Because the do
object you have inside the map is under the bucket of "ABC".hashCode()
; there is nothing in the "DEF".hashCode()
bucket.
The hashCode of the Objects in map should not change once added to map. The best way to ensure it is that the fields on which hashCode depends must be immutable.
Here is your clue:
hashcode() on both objects returns similar value
For the objects to be considered equal, their hash codes shouldn't just be similar, they must be identical.
If two objects have different hash codes, then as far as the container is concerned the objects are different. There's no need to even call equals()
.
From the Javadoc:
The general contract of
hashCode
is:
- If two objects are equal according to the
equals(Object)
method, then calling thehashCode
method on each of the two objects must produce the same integer result.
If I were you, I'd take a close look at DomainObject.hashcode()
and DomainObject.equals()
to see what's causing the contract to be broken.
map.get(do)
returning null
could be easily explained by assuming that the Boolean
value for that key might be null
but map.containsKey(do)
returning false
would require do
's hashCode
to be different at the time of calling containsKey(do)
to it's hashCode
at the time of retrieving it from the keySet
.
To see what's happening, you could (temporarily) use a more verbose implementation of HashMap... Maybe something like this:
public class VerboseHashMap<K, V> implements Map<K, V> {
private transient final static Logger logger = Logger.getLogger(VerboseHashMap.class);
private HashMap<K, V> internalMap = new HashMap<K, V>();
public boolean containsKey(Object o) {
logger.debug("Object HashCode: " + o.hashCode());
logger.debug("Map contents:");
for (Entry<K, V> entry : internalMap.entrySet()) {
logger.debug(entry.getKey().hashCode() + " - " + entry.getValue().toString());
}
return internalMap.containsKey(o);
}
public V get(Object key) {
logger.debug("Object HashCode: " + key.hashCode());
logger.debug("Map contents:");
for (Entry<K, V> entry : internalMap.entrySet()) {
logger.debug(entry.getKey().hashCode() + " - " + entry.getValue().toString());
}
return internalMap.get(key);
}
}
You'd need to map all the other requirements of the Map interface to your internalMap as well.
Note: This code is not intended for production, nor is it in any way performance oriented, nice or unsmelly....
2nd note (after seeing your code): To use your domain-object as a key for your hashMap, you should only use the immutable parts of your object for hashCode and equals (in this case the id-value). Else lazy-loading further values would change the hashCode...
In Response to your comment:
public class Demo extends TestCase {
public void testMap() {
Map<DomainObject, String> map = new HashMap<DomainObject, String>();
DomainObject sb = new DomainObject();
map.put(sb, "Some value");
System.out.println(map.containsKey(sb));
sb.value = "Some Text";
System.out.println(map.containsKey(sb));
}
private static class DomainObject {
public String value = null;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DomainObject other = (DomainObject) obj;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
}
prints
true
false
The HashCode for the key is computed at the time of putting it into the map.