【问题标题】:Why am I getting duplicate keys in Java HashMap? [duplicate]为什么我在 Java HashMap 中得到重复的键? [复制]
【发布时间】:2023-03-29 15:59:02
【问题描述】:

我似乎在标准 Java HashMap 中得到了重复的键。通过“重复”,我的意思是键通过它们的equals() 方法是相等的。这是有问题的代码:

import java.util.Map;
import java.util.HashMap;

public class User {
    private String userId;
    public User(String userId) { 
        this.userId = userId;
    }
    public boolean equals(User other) {
        return userId.equals(other.getUserId());
    }
    public int hashCode() {
        return userId.hashCode();
    }
    public String toString() {
        return userId;
    }

    public static void main(String[] args) {
        User arvo1 = new User("Arvo-Part");
        User arvo2 = new User("Arvo-Part");
        Map<User,Integer> map = new HashMap<User,Integer>();
        map.put(arvo1,1);
        map.put(arvo2,2);

        System.out.println("arvo1.equals(arvo2): " + arvo1.equals(arvo2));
        System.out.println("map: " + map.toString());
        System.out.println("arvo1 hash: " + arvo1.hashCode());
        System.out.println("arvo2 hash: " + arvo2.hashCode());
        System.out.println("map.get(arvo1): " + map.get(arvo1));
        System.out.println("map.get(arvo2): " + map.get(arvo2));
        System.out.println("map.get(arvo2): " + map.get(arvo2));
        System.out.println("map.get(arvo1): " + map.get(arvo1));
    }
}

这是结果输出:

arvo1.equals(arvo2): true
map: {Arvo-Part=1, Arvo-Part=2}
arvo1 hash: 164585782
arvo2 hash: 164585782
map.get(arvo1): 1
map.get(arvo2): 2
map.get(arvo2): 2
map.get(arvo1): 1

如您所见,两个User 对象上的equals() 方法返回true,并且它们的哈希码相同,但它们各自在map 中形成一个不同的key。此外,map 继续区分最后四个 get() 调用中的两个 User 键。

这与documentation直接矛盾:

更正式地说,如果此映射包含从键 k 到值 v 的映射,满足 (key==null ? k==null : key.equals(k)),则此方法返回 v;否则返回null。 (最多可以有一个这样的映射。)

这是一个错误吗?我在这里错过了什么吗?我正在运行通过 Homebrew 安装的 Java 版本 1.8.0_92。

编辑:此问题已被标记为此other question 的重复,但我将保留此问题,因为它识别出与equals() 的看似不一致,而另一个问题假定错误在于@987654335 @。希望这个问题的存在将使这个问题更容易搜索。

【问题讨论】:

  • 尝试将@Override 添加到您的equalshashCode 方法中(始终是最佳实践),看看是否能获得任何有用的信息。
  • 为了允许这种错别字或错误,以后总是让你的IDE为你生成方法。然后调整它们,使它们看起来像你想要的。这将使用 @Override 注释创建正确的方法。

标签: java hashmap equals hashcode


【解决方案1】:

问题在于您的equals() 方法。 Object.equals() 的签名是equals(OBJECT),但在你的情况下是equals(USER),所以这是两种完全不同的方法,hashmap 调用带有Object 参数的方法。您可以通过在您的等号上放置 @Override 注释来验证这一点 - 它会生成编译器错误。

equals方法应该是:

  @Override
  public boolean equals(Object other) {
    if(other instanceof User){
        User user = (User) other;
        return userId.equals(user.userId);
    }

    return false;
}

作为最佳实践,您应该始终将 @Override 放在您覆盖的方法上 - 它可以为您省去很多麻烦。

【讨论】:

  • 对我来说有趣的问题:动态类型不应该调用最匹配的调用吗? Object o = new User(); -&gt; o.equals(o) 可以分派到运行时类型的最佳匹配。 - 但是 Java 使用静态编译时类型来查找正确的重载,而不是动态运行时参数 - 您可以将其包含在您的答案中以确保完整性!
  • 它将被分派到最佳的“编译时间”类型。 java没有动态类型
  • 嗯,你错过了 hasmap 被编译为总是调用equals(object),所以如果你有equals(user),它永远不会调用它。但是,如果您在代码中执行User user = new User(); user.equals(new User()),则会调用equals(User)。但是,如果您按照示例 Object user = new User(); user.equals(new User()) 进行操作,则将调用 equals(object)。我希望清除它
  • 这就清除了!我认为将其添加到您对其他用户的回答中可能是有益的:-)
  • 除非明确保证派生类不会成为问题,否则我会避免在 equals 中使用 instanceof。
【解决方案2】:

你的equals方法没有覆盖equalsMap中的类型在运行时被擦除,所以实际调用的equals方法是equals(Object)。你的 equals 应该看起来更像这样:

@Override
public boolean equals(Object other) {
    if (!(other instanceof User))
        return false;
    User u = (User)other;
    return userId.equals(u.userId);
}

【讨论】:

    【解决方案3】:

    就像其他人回答的那样,您的 equals 方法签名有问题。根据 Java equals 最佳实践,您应该像下面这样实现 equals:

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

    hashCode() 方法也是如此。见Overriding equals() and hashCode() method in Java

    第二个问题

    你现在没有重复了,但是你有一个新问题,你的HashMap 只包含一个元素:

    map: {Arvo-Part=2}
    

    这是因为两个User 对象都引用了相同的字符串(JVM String Interning),从HashMap 的角度来看,您的两个对象是相同的,因为这两个对象在 hashcode 和 equals 方法中是等效的。因此,当您将第二个对象添加到 HashMap 时,您会覆盖第一个对象。 为避免此问题,请确保为每个用户使用唯一 ID

    对用户的简单演示:

    【讨论】:

      【解决方案4】:

      好的,首先,代码无法编译。缺少此方法:

      other.getUserId()
      

      但除此之外,您还需要@Override equals 方法,像Eclipse 这样的IDE 也可以帮助生成equalshashCode btw。

      @Override
      public boolean equals(Object obj)
      {
        if(this == obj)
           return true;
        if(obj == null)
           return false;
        if(getClass() != obj.getClass())
           return false;
        User other = (User) obj;
        if(userId == null)
        {
           if(other.userId != null)
              return false;
        }
        else if(!userId.equals(other.userId))
           return false;
        return true;
      }
      

      【讨论】:

        【解决方案5】:

        正如 Chrylis 建议的那样,通过将 @Override 添加到 hashCodeequals 您将收到编译错误,因为 equals 方法的签名是 public boolean equals(Object other),因此您实际上并没有覆盖默认值(来自 Object 类)equals 方法。这会导致两个用户最终进入hashMap 内的同一个桶中(hashCode 被覆盖并且两个用户具有相同的哈希码),但是当检查相等性时,它们是不同的,因为使用了默认的 equals 方法,这意味着比较内存地址。

        equals 方法替换为以下内容以获得预期结果:

        @Override
        public boolean equals(Object other) {
            return getUserId().equals(((User)other).getUserId());
        }
        

        【讨论】:

        • equals 不应抛出(如果类型不匹配,您的版本会抛出)。
        猜你喜欢
        • 2015-01-22
        • 2015-07-16
        • 1970-01-01
        • 2019-11-11
        • 1970-01-01
        • 1970-01-01
        • 2021-07-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多