我正在尝试编写一个服务器,该服务器使用HashMap<ClientID,Client>
通过唯一生成的ID跟踪其客户端。我的想法是,如果我是管理员而我想从服务器引导某人,我会查找相应的ClientID(它实际上只是一个字符串;唯一的区别是ClientID类确保没有两个客户端的工作曾为该客户分配相同的ID)然后输入命令,如“踢12”(如果我想踢的人的ClientID恰好是12)。
我认为这样可行,因为我认为HashMap
可能是由继承自Object的hashCode()方法的内部使用支持的,我设计了ClientID类,以支持必要的查找操作,假设是真正。但显然,事实并非如此 - 具有相同哈希码的两个密钥显然不被认为是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方法完全无法工作,因为我打算它);我该如何解决这个问题?
答案 0 :(得分:3)
在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);
}
答案 1 :(得分:2)
HashSet
(和HashMap
)使用Object.hashCode
方法确定对象应该进入哪个哈希桶,但不该对象是否等于另一个对象也在那个桶里。为此,他们使用Object.equals
。在您的情况下,您尝试使用String ID的引用相等来实现该方法 - 不是“实际”相等,而是字符串相等。您还创建了equals
的新重载,而不是覆盖Object.equals
。
您可以在SO上搜索有关无法使用==
无法比较字符串的原因的大量问题,但是tl; dr版本是您需要覆盖boolean equals(Object)
(不是一个相同名称的重载方法,但该方法完全 - 它必须采用Object
)并检查传入对象是否为其字符串标识为equals
的ClientID(ont {{ 1)} s)此ClientID的字符串ID。
答案 2 :(得分:0)
BTW所有读过这篇文章的人:
你应该小心任何有趣的Java Collection
它是孩子的哈希码,在它的子类型的情况下
hashcode取决于它的可变状态。
一个例子:
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)
结束代码
-reason being是HashSet remove方法使用HashMap 它通过hashCode识别键,而AbstarctSet的hashCode 是动态的,取决于它自身的可变特性。
希望有所帮助