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

为什么我会在Java HashMap中得到重复的键?,java,hashmap,equals,hashcode,Java,Hashmap,Equals,Hashcode,我似乎在标准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 boolea

我似乎在标准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));
    }
}
如您所见,两个
User
对象上的
equals()
方法返回
true
,它们的哈希代码相同,但它们在
map
中各自形成一个不同的
键。此外,
map
继续区分最后四个
get()
调用中的两个
User

这直接违背了:

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

这是虫子吗?我是不是遗漏了什么?我运行的是Java版本1.8.092,我是通过自制软件安装的


编辑:此问题已被标记为此问题的副本,但我将保留此问题,因为它确定了与
equals()
的表面不一致,而另一个问题假定错误在于
hashCode()
。希望此问题的出现将使此问题更易于搜索。

您的equals方法不会覆盖
equals
,并且
映射中的类型在运行时被擦除,因此实际调用的equals方法是
equals(Object)
。你的同龄人应该更像这样:

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

问题在于您的
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
,这样可以节省很多麻烦。

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

other.getUserId()
但除此之外,您还需要重写equals方法,像Eclipse这样的IDE也可以帮助生成equals和hashCode

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

正如Chrylis所建议的,通过将
@Override
添加到
hashCode
equals
中,您将得到一个编译错误,因为
equals
方法的签名是
public boolean equals(Object other)
,因此您实际上没有覆盖默认的(从对象类)equals方法。这会导致两个用户最终都位于
hashMap
中的同一个bucket中(hashCode被覆盖,并且两个用户都有相同的hash代码),但是当检查是否相等时,它们是不同的,因为使用了默认的equals方法,这意味着要比较内存地址

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

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

像其他人回答的一样,您对
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()
方法。看

第二个问题

现在不再有重复项,但出现了一个新问题,
HashMap
只包含一个元素:

map: {Arvo-Part=2}
这是因为两个
User
对象都引用了相同的String(),并且从
HashMap
的角度来看,两个对象是相同的,因为这两个对象在hashcode和equals方法中是等价的。因此,当您将第二个对象添加到
HashMap
时,您将覆盖第一个对象。 要避免此问题,请确保为每个用户使用唯一的ID

对用户的简单演示:


尝试将
@Override
添加到您的
equals
hashCode
方法中(始终是一种最佳实践),看看您是否获得了任何有用的信息。若要在将来允许此类键入或错误,请始终让您的IDE为您生成方法。然后调整它们,使它们看起来像你想要的。这将创建带有
@Override
注释的正确方法。
equals
不应抛出(如果类型不匹配,您的版本将抛出)。我感兴趣的问题是:动态类型不应该调用调用的最佳匹配吗<代码>对象o=新用户();->o、 equals(o)
可以调度到运行时类型的最佳匹配。-但是Java使用静态编译时类型来查找正确的重载,而不是动态运行时参数——为了完整性,您可以在答案中包含这些参数!它将被分派到最佳的“编译时”类型。java没有动态键入hmm,您缺少hasmap被编译为总是调用
equals(object)
,因此如果您有
equals(user)
,它将永远不会调用它。但是如果您在代码中执行
User User=new User();user.equals(new user())
,将调用
equals(user)
。但是如果你像你的例子中那样做
objectuser=newuser();user.equals(new user())
将调用
equals(对象)
。我希望那会让它变干净那会让它变干净的!我认为将它添加到您的答案中对其他用户可能是有益的:-)我将避免使用equals中的instanceof,除非明确保证派生类不会成为问题。
map: {Arvo-Part=2}