【问题标题】:Java - HashMap and HashSet not backed by Object.hashCode()?Java - Object.hashCode() 不支持 HashMap 和 HashSet?
【发布时间】:2012-06-24 09:27:30
【问题描述】:

我正在尝试编写一个服务器,它使用HashMap<ClientID,Client> 通过唯一生成的 ID 跟踪其客户端。这个想法是,如果我是管理员并且我想将某人从服务器上启动,我会查找适当的 ClientID(这实际上只是一个字符串;唯一的区别是 ClientID 类的工作是确保没有两个客户端是曾经为该客户端分配了相同的 ID),然后输入诸如“kick 12”之类的命令(如果我想踢的人的 ClientID 恰好是 12)。 我认为这会起作用,因为我认为 HashMap 可能是由从 Object 继承的 hashCode() 方法的内部使用支持的,并且我以支持必要查找操作的方式设计了 ClientID 类,假设这是真的。但显然,事实并非如此——具有相同哈希码的两个键显然不被认为是HashMap(或HashSet)中的相同键。 我创建了一个使用HashSet 的简单示例来说明我想要做什么:

导入java.lang.*; 导入java.io.*; 导入 java.util.*; 类客户 ID { 私有字符串 id; 公共客户端 ID(字符串 myId) { 身份证=我的身份证; } 公共静态 ClientID generateNew(Set 存在) { ClientID res = new ClientID(""); 随机 rand = new Random(); 做 { int p = rand.nextInt(10); res.id += p; } 而 (existing.contains(res)); 返回资源; } 公共 int hashCode() { 返回(id.hashCode()); } 公共布尔等于(字符串 otherID) { 返回(id == otherID); } 公共布尔等于(ClientID 其他) { 返回(id == other.id); } 公共字符串 toString() { 返回标识; } 公共静态 void main(String[] args) 抛出 IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); HashSet mySet = new HashSet(); ClientID myId = ClientID.generateNew(mySet); mySet.add(myId); 字符串输入; 做 { System.out.println("集合中的ID/哈希码列表:"); for (ClientID x: mySet) System.out.println("\t" + x.toString() + "\t" + x.hashCode()); System.out.print("\n输入一个ID来测试它是否在集合中:"); 输入 = in.readLine(); 如果(输入==空) 休息; 否则 if (input.length() == 0) 继续; ClientID matchID = new ClientID(input); if (mySet.contains(matchID)) System.out.println("成功!Set 已经包含该 ID :)"); 别的 { System.out.println("添加 ID " + matchID.toString() + " (hashcode " + matchID.hashCode() + ") 到集合"); mySet.add(matchID); } System.out.println("\n"); } while (!input.toUpperCase().equals("QUIT")); } }

使用此代码,不可能(据我所知)产生输出

成功了! Set 已经包含该 ID :)

... 相反,它只会继续向该集合添加值,即使这些值是重复的(也就是说,它们与 equals 方法相等并且它们具有相同的哈希码)。如果我没有很好地沟通,请自己运行代码,我想你会很快明白我的意思......这使得查找变得不可能(这也意味着 Client.generateNew 方法根本不像我想要的那样工作它到);我该如何解决这个问题?

【问题讨论】:

  • 有一点是肯定的,构造函数应该是私有的
  • 它是私有的,在我的原始代码中(超过 1000 行,所以我不想在这里发布)。我只是为了更容易编写这个例子而将其公开,我就是这样懒惰:)

标签: java hashmap hashset lookup-tables


【解决方案1】:

在 Java 中,要使特定类充当哈希中的键,它必须实现两种方法。

public int hashCode();
public boolean equals(Object o);

这些方法必须连贯地操作:如果一个对象等于另一个对象,则这些对象必须产生相同的散列。

注意equals(Object o) 的签名。您的 equals 方法正在重载 equals,但您必须覆盖 equals(Object o)

正如其他人所指出的那样,您覆盖的 equals 方法也已损坏,因为您正在比较 String 身份,而不是价值。不要通过str1 == str2 进行比较,而是使用str1.equals(str2)

对您的代码进行以下修改,事情应该会开始正常工作。

public boolean equals(Object o){
    return o instanceof ClientID ? this.equals((ClientID) o);
}

public boolean equals(String otherID) {
    return id.equals(otherID);
}

public boolean equals(ClientID other) {
    return id.equals(other.id);
}

【讨论】:

  • 顺便说一下,我还需要更改现有的重载。在我的原始代码中,重载调用 == 运算符,而它们本应调用 equals 方法。嗬!但是在更正了这一点并添加了代码之后,一切似乎都井井有条。谢谢!
  • @user1473824 事实上,我根据这个事实修改了我的帖子。我把id 误认为int
【解决方案2】:

HashSet(和HashMap)使用Object.hashCode 方法来确定对象应该进入哪个哈希桶,但该对象是否等于该桶中的另一个对象.为此,他们使用Object.equals。在您的情况下,您尝试使用字符串 ID 的引用相等来实现该方法——不是“实际”相等,而是字符串相等。您还创建了 equals 的新重载,而不是覆盖 Object.equals

您可以在 SO 上搜索很多关于为什么无法使用 == 比较 String 的问题,但 tl;dr 版本是您需要覆盖 boolean equals(Object)不是 一个同名的重载方法,但该方法完全正确——它必须采用Object) 并检查传入对象是一个ClientID,其String id equals (ont ==s) 这个ClientID 的String id。

【讨论】:

  • 好电话,我没注意到 id 是String
【解决方案3】:

顺便说一句,所有阅读这篇文章的人:

你都应该小心任何有缺陷的 Java 集合 它是哈希码的孩子,如果它是孩子类型的 哈希码取决于它的可变状态。 一个例子:

HashSet<HashSet<?>> or HashSet<AbstaractSet<?>> or HashMap varient:

HashSet 通过它的 hashCode 检索它的项目,但它是项目类型 是一个 HashSet,hashSet.hashCode 取决于它的项目状态。

相关代码:

HashSet<HashSet<String>> coll = new HashSet<HashSet<String>>();
HashSet<String> set1 = new HashSet<String>();
set1.add("1");
coll.add(set1);
print(set1.hashCode); //---> will output X
set1.add("2");
print(set1.hashCode); //---> will output Y
coll.remove(set1) // WILL FAIL TO REMOVE (SILENTLY)

结束代码

-原因是 HashSet remove 方法使用 HashMap 它通过 hashCode 识别键,而 AbstarctSet 的 hashCode 是动态的,取决于自身的可变属性。

希望对你有帮助

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-05
    • 2015-02-05
    • 2011-02-15
    • 1970-01-01
    相关资源
    最近更新 更多