Java HashMap does not find the key, but it must

I have a strange problem arising in my application, I will quickly explain the global architecture, and then my problem in depth.

I use the service to populate the HashMap<DomainObject,Boolean> coming from my database (JPA), which in turn returns to my view by calling the remote EJB method (using Apache Wicket). In this part, I add a new DomainObject to the map returned to store any new value from my end user.

The problem occurs when the user clicks the "add" button in his browser, I try to restore the newly created item on my map, but it fails. When playing with the debugger, I come across the following things.

Assuming the HashMap<DomainObject, Boolean> map and DomainObject do are interesting two variables, I have the following results in the debugger

map.keySet(); gives me an object corresponding to do (even if any simili reference is identical), hashcode() for both objects returns the same value and equals() between the two return values true

map.containsKey(do); returns false

map.get(do) ; returns null , strange because my key is in map .

Assuming my newly created element is the first key listed by keySet() , I do the following: map.get(new ArrayList(map.keySet()).get(0)) and it returns null.

If this can help, adding breakpoints to my DomainObject.equals() and DomainObject.hashcode() methods, I found that map.get() only calls hashcode() , not equals() .

The only workaround I found was to recreate a new map on top of the existing new HashMap(map) , on this new map, I have no problem finding an object by its key.

I hope someone here can give me a pointer to what is happening, thanks.

Used environment:

  • Sun Java 1.6.0_26 x64 under OS X 10.7.1
  • OpenJDK 1.6.0_18 x64 on Debian 6.0.2 (2.6.32)
  • Apache Wicket 1.4.17
  • Oracle Glassfish 3.1.1
  • JBoss Hibernate 3.6.5

DomainObject code:

 public class AssetComponentDetailTemplate extends BaseEntite<Long> { public enum DataType { TXT, DATE, INT, JOIN, LIST, COULEURS, REFERENCE } public enum Tab { IDENTITE, LOCALISATION, CYCLE_DE_VIE, FINANCE, RESEAU, DETAIL } @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(nullable = false) private String name; @Column(nullable = false) @Enumerated(EnumType.STRING) private DataType dataType; private Integer classNameId; private Long orderId; private Long nextAssetComponentDetailTemplateId; private String unit; @Enumerated(EnumType.STRING) private Tab tab; @Column(nullable = false) private Long uniqueOrganizationId; @OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "idAssetComponentDetailTemplate", insertable = false, updatable = false) private List<AssetComponentDetailJoin> assetComponentDetailJoins; private Boolean mandatory = false; public AssetComponentDetailTemplate() { } public Long getId() { return id; } public void setId(final Long id) { this.id = id; } public String getName() { return name; } public void setName(final String name) { this.name = name; } public DataType getDataType() { return dataType; } public void setDataType(final DataType dataType) { this.dataType = dataType; } public Integer getClassNameId() { return classNameId; } public void setClassNameId(final Integer classNameId) { this.classNameId = classNameId; } public Long getUniqueOrganizationId() { return uniqueOrganizationId; } public void setUniqueOrganizationId(final Long uniqueOrganizationId) { this.uniqueOrganizationId = uniqueOrganizationId; } public Long getNextAssetComponentDetailTemplateId() { return nextAssetComponentDetailTemplateId; } public void setNextAssetComponentDetailTemplateId(final Long nextAssetComponentDetailTemplateId) { this.nextAssetComponentDetailTemplateId = nextAssetComponentDetailTemplateId; } public String getUnit() { return unit; } public void setUnit(final String unit) { this.unit = unit; } public Tab getTab() { return tab; } public void setTab(final Tab tab) { this.tab = tab; } public Long getOrder() { return orderId; } public void setOrder(final Long order) { this.orderId = order; } public Boolean isMandatory() { return mandatory; } @Override public String toString() { return name; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final AssetComponentDetailTemplate that = (AssetComponentDetailTemplate) o; if (classNameId != null ? !classNameId.equals(that.classNameId) : that.classNameId != null) { return false; } if (dataType != that.dataType) { return false; } if (id != null ? !id.equals(that.id) : that.id != null) { return false; } if (name != null ? !name.equals(that.name) : that.name != null) { return false; } if (nextAssetComponentDetailTemplateId != null ? !nextAssetComponentDetailTemplateId.equals(that.nextAssetComponentDetailTemplateId) : that.nextAssetComponentDetailTemplateId != null) { return false; } if (orderId != null ? !orderId.equals(that.orderId) : that.orderId != null) { return false; } if (tab != that.tab) { return false; } if (uniqueOrganizationId != null ? !uniqueOrganizationId.equals(that.uniqueOrganizationId) : that.uniqueOrganizationId != null) { return false; } if (unit != null ? !unit.equals(that.unit) : that.unit != null) { return false; } return true; } @Override public int hashCode() { int result = id != null ? id.hashCode() : 0; result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + (dataType != null ? dataType.hashCode() : 0); result = 31 * result + (classNameId != null ? classNameId.hashCode() : 0); result = 31 * result + (orderId != null ? orderId.hashCode() : 0); result = 31 * result + (nextAssetComponentDetailTemplateId != null ? nextAssetComponentDetailTemplateId.hashCode() : 0); result = 31 * result + (unit != null ? unit.hashCode() : 0); result = 31 * result + (tab != null ? tab.hashCode() : 0); result = 31 * result + (uniqueOrganizationId != null ? uniqueOrganizationId.hashCode() : 0); return result; } 
+4
source share
4 answers

[This basically extends Jesper's answer, but the details may help you]

Since re-creating the map using new HashMap(map) allows you to find the element, I suspect that the hashCode() of the DomainObject has changed after adding it to the map.

For example, if your DomainObject looks like this

 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 . Since the do object that you have inside the map is under the "ABC".hashCode() bucket; there is nothing in the "DEF".hashCode() .

The hash code of objects on the map should not change after adding to the map. The best way to ensure that the fields that hashCode depends on must be unchanged .

+4
source

Here is your key:

hashcode () for both objects returns the same value

In order for the objects to be considered equal, their hash codes must not be similar, they must be identical.

If two objects have different hash codes, then the objects are different with respect to the container. There is no need to even call equals() .

From Javadoc :

The general hashCode contract is:

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects should have the same integer result.

If I were you, I would carefully look at DomainObject.hashcode() and DomainObject.equals() to find out what violates the contract.

+3
source

Is your DomainObject class immutable? Are hashCode and equals methods implemented correctly?

Please note that you will have problems if your DomainObject class DomainObject not immutable and you change the state of the object while on the map in such a way as to 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 java.lang.Object.hashCode() API documentation for more details.

+3
source

map.get(do) return null can be easily explained by assuming that the Boolean value for this key can be null , but map.containsKey(do) returning false will require that do hashCode different by the time containsKey(do) calls hashCode on it hashCode during retrieval time from keySet .

To find out what is happening, you can (temporarily) use a more detailed implementation of the 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 will also need to match all the other requirements of the map interface with your internal card.

Note. This code is not intended for production, nor is it in any way performance-oriented, pleasant or invisible ....

2nd note (after looking at the code): To use your domain object as a key for your hash map, you must use the immutable parts of your object for hashCode and are equal (in this case, the id value). The rest of the lazy-load additional values ​​would change 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 calculated when it is placed on the map.

0
source

All Articles