【问题标题】:Java HashMap not finding key, but it shouldJava HashMap 找不到键,但它应该
【发布时间】:2011-09-27 07:03:14
【问题描述】:

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

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

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

假设HashMap<DomainObject, Boolean> 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 (2.6.32) 下的 OpenJDK 1.6.0_18 x64
  • Apache Wicket 1.4.17
  • Oracle Glassfish 3.1.1
  • JBoss 休眠 3.6.5

DomainObject代码:

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;
}

【问题讨论】:

  • 你应该显示一些代码。至少你如何管理地图以及equalshashCode的实现。
  • 我们可以看看一些代码吗?
  • 恐怕提取代码不会很容易(它在应用程序中是紧密耦合的)。我添加DomainObject 代码。

标签: java map


【解决方案1】:

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

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

例如,如果您的 DomainObject 如下所示

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

然后

   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); 

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

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

【讨论】:

    【解决方案2】:

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

    请注意,如果您的 DomainObject 类不是不可变的,并且您更改对象在映射中的状态以更改调用 hashCodeequals 的结果,您将遇到麻烦.

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

    【讨论】:

      【解决方案3】:

      这是你的线索:

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

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

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

      来自Javadoc

      hashCode的总合约是:

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

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

      【讨论】:

      • 当我的意思是相同时,我的意思是等于价值。我发现令人惊讶的是,它可以从来自我的服务层的对象按预期工作,但不能使用创建的视图。这是我认为广泛使用的案例。如果我的hashcode() 错了,那么使用旧地图创建新地图也不起作用,对吗?
      【解决方案4】:

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

      要查看发生了什么,您可以(暂时)使用更详细的 HashMap 实现... 也许是这样的:

      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);
          }
      
      }
      

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

      注意:此代码不用于生产,也不是以任何方式面向性能、美观或难闻的......

      第二个注意事项(在查看您的代码之后):要将您的域对象用作 hashMap 的键,您应该只将对象的不可变部分用于 hashCode 和 equals(在本例中为 id 值)。否则延迟加载更多值会改变 hashCode...

      回应您的评论:

      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;
              }
      
          }
      
      }
      

      打印

      true 
      false
      

      键的 HashCode 是在将其放入映射时计算的。

      【讨论】:

      • 如果我同时在我的do 和对应于do 的键上计算hashCode(),我得到完全相同的结果,所以我会说它不是一个改变的元素我的do 对象(因为它引用了同一个对象)。在我的地图中,右侧的值要么是true,要么是false,而不是null,调试器显示我的值是false
      • 谢谢,我现在明白是怎么回事了
      猜你喜欢
      • 1970-01-01
      • 2018-12-04
      • 2012-06-25
      • 2016-09-24
      • 1970-01-01
      • 2020-09-07
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多