为什么我在Java HashMap中获得重复的键?

时间:2016-07-15 05:53:59

标签: java hashmap equals hashcode

我似乎在标准的Java HashMap中获得了重复的键。通过"复制",我的意思是密钥与test方法相同。这是有问题的代码:

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 对象上的equals()方法返回User并且它们的哈希码相同,但它们各自形成不同的truekey。此外,map继续区分过去四次map次呼叫中的两个User密钥。

这与documentation

直接相矛盾
  

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

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

编辑:此问题已被标记为此other question的副本,但我会将此问题保留为原因,因为它确定了与get()的看似不一致,而另一个问题则假设错误在于equals()。希望这个问题的存在将使这个问题更容易搜索。

5 个答案:

答案 0 :(得分:28)

问题在于您的equals()方法。 Object.equals()的签名为equals(OBJECT),但在您的情况下为equals(USER),因此这两种方法完全不同,而散列图调用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放在您覆盖的方法上 - 它可以为您节省很多麻烦。

答案 1 :(得分:13)

您的equals方法不会覆盖equals,并且Map中的类型会在运行时被删除,因此实际的equals方法称为equals(Object)。你的平等应该更像这样:

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:3)

与其他人一样,您遇到了equals方法签名的问题。根据Java等于最佳实践,您应该实现如下所示:

  @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透视图中你的两个对象是相同的,因为这两个对象在哈希码和等于方法。因此,当您将第二个对象添加到HashMap时,您将覆盖第一个对象。 要避免此问题,请确保为每个用户使用唯一ID

  

对您的用户进行简单演示:

enter image description here

答案 4 :(得分:2)

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

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

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