我正在尝试编写一个服务器,它使用 HashMap<ClientID,Client>
通过唯一生成的ID跟踪其客户端 . 我的想法是,如果我是管理员而我想从服务器引导某人,我会查找相应的ClientID(实际上只是一个字符串;唯一不同的是ClientID类确保没有两个客户端的工作为该客户端分配了相同的ID,然后输入一个命令,如"kick 12"(如果我想踢的人的ClientID恰好是12) . 我认为这样可行,因为我认为 HashMap
可能是由继承自Object的hashCode()方法的内部使用支持的,我设计ClientID类的方式将支持必要的查找操作,假设's true. But apparently, it'不是真的 - 具有相同哈希码的两个密钥显然不被认为是 HashMap
(或 HashSet
)中的相同密钥 . 我创建了一个使用 HashSet
的简单示例来说明我想要做的事情:
import java.lang.*;
import java.io.*;
import java.util.*;
class ClientID {
private String id;
public ClientID(String myId)
{
id = myId;
}
public static ClientID generateNew(Set<ClientID> existing)
{
ClientID res = new ClientID("");
Random rand = new Random();
do {
int p = rand.nextInt(10);
res.id += p;
} while (existing.contains(res));
return res;
}
public int hashCode()
{
return (id.hashCode());
}
public boolean equals(String otherID)
{
return (id == otherID);
}
public boolean equals(ClientID other)
{
return (id == other.id);
}
public String toString()
{
return id;
}
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
HashSet<ClientID> mySet = new HashSet<ClientID>();
ClientID myId = ClientID.generateNew(mySet);
mySet.add(myId);
String input;
do {
System.out.println("List of IDs/hashcodes in the set: ");
for (ClientID x: mySet)
System.out.println("\t" + x.toString() + "\t" + x.hashCode());
System.out.print("\nEnter an ID to test if it's in the set: ");
input = in.readLine();
if (input == null)
break;
else if (input.length() == 0)
continue;
ClientID matchID = new ClientID(input);
if (mySet.contains(matchID))
System.out.println("Success! Set already contains that ID :)");
else {
System.out.println("Adding ID " + matchID.toString() + " (hashcode " + matchID.hashCode() + ") to the set");
mySet.add(matchID);
}
System.out.println("\n");
} while (!input.toUpperCase().equals("QUIT"));
}
}
使用这段代码,产生输出是不可能的(据我所知)
Success! Set already contains that ID :)
...相反,它将继续向该集合添加值,即使值是重复的(即,它们与equals方法相等并且它们具有相同的哈希码) . 如果我没有很好地沟通,请为自己运行代码,我想你会很快看到我的意思......这使查找变得不可能(这也意味着Client.generateNew方法根本无法工作)它);我该如何解决这个问题?
3 回答
在Java中,要使特定类充当散列中的键,它必须实现两个方法 .
这些方法必须协调运行:如果一个对象等于另一个,则这些对象必须生成相同的哈希 .
请注意
equals(Object o)
的签名 . 您的equals
方法正在重载equals
,但您必须覆盖equals(Object o)
.正如其他人所指出的那样,您的覆盖
equals
方法也被打破了,因为您正在比较String
身份,而不是值 . 而不是通过str1 == str2
进行比较,使用str1.equals(str2)
.对您的代码进行以下修改,事情应该开始正常工作 .
HashSet
(和HashMap
)使用Object.hashCode
方法确定对象应该进入哪个哈希桶,而不是该对象是否也等于该桶中的另一个对象 . 为此,他们使用Object.equals
. 在您的情况下,您尝试使用String ID的引用相等来实现该方法 - 不是"actual"相等,而是字符串相等 . 您还创建了equals
的新重载,而不是覆盖Object.equals
.您可以在SO上搜索很多关于为什么无法使用
==
比较String的问题,但是tl; dr版本是您需要覆盖boolean equals(Object)
(不是同名的重载方法,但该方法完全 - - 它必须采用Object
)并检查传入对象是否为其ClientID的字符串id为字符串equals
(ont==
s)的ClientID .所有读过这篇文章的人都是这样的:
你应该小心任何使它成为可变状态的Java集合.655756_ s可变状态 . 一个例子:
HashSet通过它的hashCode检索它的项目,但是它的项类型是HashSet,而hashSet.hashCode依赖于它的项状态 .
代码:
结束代码
-reason being是HashSet remove方法使用HashMap并且它通过hashCode识别密钥,而AbstarctSet的hashCode是动态的并且依赖于它自身的可变属性 .
希望有所帮助