Java HashMap找不到密钥,但它应该

我的应用程序中出现了一个奇怪的问题,我将快速解释全局架构,然后深入解释我的问题。

我使用一个服务来填充来自我的数据库(JPA驱动)的HashMap ,然后通过EJB远程方法调用(使用Apache Wicket)返回到我的视图。 在这部分中,我将一个新的DomainObject添加到返回的映射中,以便存储来自我的最终用户的任何新值。

当用户在其浏览器中点击“添加”按钮时,会出现问题,我尝试在地图中检索新创建的项目,但它失败了。 通过使用调试器,我面临以下事情。

假设HashMap mapDomainObject do是两个有趣的变量我在调试器中有以下结果

map.keySet(); 给我一个对应于do的对象(即使@whatever simili-reference是相同的),两个对象上的hashcode()返回相似的值,两者之间的equals()返回true

map.containsKey(do); 返回false

map.get(do) ; 返回null ,很奇怪,因为我的键似乎在map

假设我新创建的项是keySet()枚举的第一个键,我执行以下操作: map.get(new ArrayList(map.keySet()).get(0)) ,它返回null。

如果它可以帮助,通过将断点附加到我的DomainObject.equals()DomainObject.hashcode()方法,我发现map.get()只调用hashcode()而不是equals()

我找到的唯一解决方法是在现有的一个new HashMap(map)之上重新创建一个新地图,在这个新地图中,我根本没有问题通过其键查找对象。

我希望这里有人可以指出我发生了什么,谢谢。

使用环境:

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

DomainObject代码:

 public class AssetComponentDetailTemplate extends BaseEntite { 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 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; } 

[这基本上扩展了Jesper的答案,但细节可能对你有所帮助]

由于使用new HashMap(map)重新创建地图,因此能够找到我怀疑DomainObject的hashCode()在将其添加到Map后发生更改的元素。

例如,如果您的DomainObject看起来如下

 class DomainObject { public String name; long hashCode() { return name.hashCode(); } boolean equals(Object other) { /* compare name in the two */' } 

然后

  Map m = new HashMap(); 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); 

上面的最后一个语句将返回null 。 因为你在地图中的do对象位于"ABC".hashCode()的桶中。 "DEF".hashCode()桶中没有任何内容。

一旦添加到地图中,地图中的对象的hashCode不应更改。 确保它的最佳方法是hashCode所依赖的字段必须是不可变的

您的DomainObject类是不可变的吗? 它是否正确实现了hashCodeequals方法?

请注意,如果您的DomainObject类不是不可变的,则会遇到麻烦,并且当对象在映射中时,会以更改调用hashCodeequals的结果的方式更改对象的状态。

hashCode必须以这样的方式实现,即当比较这些对象时equals返回true时,它返回两个对象的相同值。 有关详细信息,请参阅java.lang.Object.hashCode()的API文档。

这是你的线索:

两个对象上的hashcode()返回类似的值

为了使对象被认为是相等的,它们的哈希码不应该相似,它们必须是相同的。

如果两个对象具有不同的哈希码,那么就容器而言,对象是不同的。 甚至不需要调用equals()

来自Javadoc :

hashCode的一般契约是:

  • 如果两个对象根据equals(Object)方法equals(Object) ,则对两个对象中的每一个调用hashCode方法必须生成相同的整数结果。

如果我是你,我会仔细查看DomainObject.hashcode()DomainObject.equals() ,看看是什么导致合同被破坏。

map.get(do)返回null可以很容易地解释为假设该键的Boolean值可能为nullmap.containsKey(do)返回false将要求在调用containsKey(do)dohashCode不同containsKey(do)keySet检索它时的hashCode

要查看正在发生的事情,您可以(暂时)使用更详细的HashMap实现…可能是这样的:

 public class VerboseHashMap implements Map { private transient final static Logger logger = Logger.getLogger(VerboseHashMap.class); private HashMap internalMap = new HashMap(); public boolean containsKey(Object o) { logger.debug("Object HashCode: " + o.hashCode()); logger.debug("Map contents:"); for (Entry 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 entry : internalMap.entrySet()) { logger.debug(entry.getKey().hashCode() + " - " + entry.getValue().toString()); } return internalMap.get(key); } } 

您还需要将Map接口的所有其他要求映射到internalMap。

注意:此代码不适用于生产,也不以任何方式面向性能,良好或不良……

第二个注释(在看到你的代码之后):要使用你的域对象作为hashMap的一个键,你应该只使用你的对象的不可变部分用于hashCode和equals(在这种情况下是id-value)。 否则延迟加载更多值将改变hashCode …

在回复您的评论时:

 public class Demo extends TestCase { public void testMap() { Map map = new HashMap(); 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; } } } 

版画

 true false 

密钥的HashCode是在将其放入地图时计算的。