开发者

Java HashMap not finding key, but it should

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 HashMap<DomainObject,Boolean> coming from my database (JPA driven) which is in turn returned to my view, via an EJB remote method call (using Apache Wicket). In this part, I add a new DomainObject to the map returned in order to store any new value from my end user.

The problem occurs when the user hit the "add" button in its browser, I try to retrieve the newly created item in my map, but it fails. By playing with the debugger I face the following things.

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

map.keySet(); gives me an object corresponding to do (even the @whatever simili-reference is identical), hashcode() on both objects returns similar value and equals() between the two returns true

map.containsKey(do); returns false

map.get(do); returns null, weird because my key seems to be in the map.

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

If it can help, by attaching breakpoints to my DomainObject.equals() and DomainObject.hashcode() methods I found that map.get() is only calling hashcode() and not equals().

The only workaround I found is to recreate a new map on top of the existing one new HashMap(map), in this new map, I have no problem at all looking up an object by its key.

I hope someone here can give my a pointer on what happens, thanks.

Environment used :

  • Sun Java 1.6.0_26 x64 under OS X 10.7.1
  • OpenJDK 1.6.0_18 x64 under 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 Lo开发者_StackOverflowng 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;
}


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


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.


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 the hashCode 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜