[英]Java - HashMap and HashSet not backed by Object.hashCode()?
我正在嘗試編寫服務器,該服務器使用HashMap<ClientID,Client>
通過唯一生成的ID跟蹤其客戶HashMap<ClientID,Client>
。 這個想法是,如果我是管理員,並且想從服務器上引導某人,那么我會查找適當的ClientID(它實際上只是一個String;唯一的區別是ClientID類所做的工作是確保沒有兩個客戶端曾經為該客戶分配相同的ID),然后輸入諸如“ kick 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方法根本無法正常工作)它來); 我該如何解決?
在Java中,要使特定類充當哈希中的鍵,它必須實現兩個方法。
public int hashCode();
public boolean equals(Object o);
這些方法必須一致地操作:如果一個對象等於另一個,則這些對象必須產生相同的哈希。
注意equals(Object o)
的簽名。 您的equals
方法是重載 equals
,但是您必須重寫 equals(Object o)
。
正如其他人所指出的那樣,您覆蓋的equals
方法也被破壞了,因為您正在比較String
身份,而不是value。 而不是通過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);
}
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。
順便說一句,所有閱讀這篇文章的人:
uou都應該注意任何用哈希碼對子代進行賦值的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是動態的,並且依賴於其自身的可變屬性。
希望能有所幫助
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.