【问题标题】:ContainsKey of HashMap does not work with a Custom Class as keyHashMap 的 ContainsKey 不适用于自定义类作为键
【发布时间】:2014-04-19 18:32:43
【问题描述】:

我有一个自定义类 MarioState,我想在 HashMap 中使用它。该类表示马里奥游戏的状态空间中的可能状态。下面是 MarioState 类的简化版本。

在我的 HashMap 中,我想存储这些状态。然而,在比较两个 MarioState 时,MarioState 中的任何属性都不是应该考虑的。例如,如果一个 MarioState 的卡住属性设置为 true 并且距离为 30,而另一个 MarioState 的卡住属性也设置为 true 但距离值不同(例如 20),那么它们仍然应该被视为相同。

我知道要在我的 HashMap 中工作,我必须实现 .equals() 和 .hashcode() 方法,这就是我所做的(通过让 InteliJ IDE 自动生成它们)。

public class MarioState{

    // Tracking the distance Mario has moved.
    private int distance;
    private int lastDistance;

    // To keep track of if Mario is stuck or not.
    private int stuckCount;
    private boolean stuck;

    public MarioState(){
        stuckCount = 0;
        stuck = false;

        distance = 0;
        lastDistance = 0;
    }

    public void update(Environment environment){

        // Computing the distance
        int tempDistance = environment.getEvaluationInfo().distancePassedPhys;
        distance = tempDistance - lastDistance;
        lastDistance = tempDistance;

        // If Mario hasn't moved for over 25 turns then this means he is stuck.
        if(distance == 0){
            stuckCount++;
        } else {
            stuckCount = 0;
            stuck = false;
        }

        if(stuckCount > 25){ stuck = true; }
    }

    public float calculateReward(){
        float reward = 0f;
        reward += distance * 2;
        if(stuck){ reward += -20; }
        return reward;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        MarioState that = (MarioState) o;

        if (stuck != that.stuck) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return (stuck ? 1 : 0);
    }  
}

然而问题是,在运行代码时,某些键被认为是不同的,而不应该根据它们的 .equals() 和 .hashcode() 函数。什么可能导致这种情况?我是不是忘记了什么?

在HashMap中插入状态时使用的代码(必要时可提供额外信息):

public float[] getActionsQValues(MarioState state){
    if(!table.containsKey(state)) {
        float[] initialQvalues = getInitialQvalues(state);
        table.put(state, initialQvalues);
        return initialQvalues;
    }
    return table.get(state);
}

我处于调试模式时的屏幕截图显示我的表包含两个具有不同值的键,但键本身是相同的(但在 HashMap 中它被认为是不同的)。

【问题讨论】:

  • 你有没有机会改变stuck 添加元素到地图?这肯定会解释它。
  • @JonSkeet 简短回答:是的。长答案:我有另一个类作为变量跟踪当前状态。每个时间步都会从 MarioState 调用更新方法,并在状态发生变化时对其进行修改。然后调用 getActionsQValues 来检查修改后的状态和 HashMap 中的状态。只有当状态对 HashMap 来说是全新的(即 HashMap 中尚未出现的唯一属性组合)时,才应该输入它。
  • 简短回答:你已经打破了 HashMap 假设,即在你添加它之后,键的哈希码不会改变 :) 有一个带有相等键的映射是非常奇怪的老实说,基于单个 boolean 值。
  • @JonSkeet 你是对的。我通过让 update() 方法返回一个新的 MarioState 来稍微更改我的代码,这似乎可以解决它。非常感谢你!至于相等性,它实际上不仅基于该布尔值。这只是状态的一个属性,但状态可以拥有更多的属性(我只发布了我实际使用的状态的简化版本)。使用完整的 MarioState,我有多个具有离散值的属性,这些属性可以导致数百个独特的组合(所有都将存储在 HashMap 中)。然后平等基于所有属性。
  • @JonSkeet 如果您从您的评论中做出回答,我会接受。感谢您的帮助!

标签: java hashmap key equals hashcode


【解决方案1】:

您的哈希码计算和相等性比较均基于stuck - 但随着时间的推移会发生变化。

如果您在将对象添加为哈希映射中的键后对其进行了变异,从而导致哈希码发生变化,那么当您稍后请求它时将找不到该键 - 因为存储的哈希码是首次添加的密钥将不再与其当前的哈希码相同。

尽可能避免在映射中使用可变对象作为键(即使是不使用哈希码的TreeMap,如果您以一种会改变相对顺序的方式更改对象,也会有同样的问题) .如果您必须在映射中使用可变对象作为键,则应避免在将它们添加为键后对其进行变异。

【讨论】:

  • 感谢您的信息和提示!我会看看我是否可以使用不可变对象作为键(或避免改变它们)。
猜你喜欢
  • 1970-01-01
  • 2011-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多