Java - HashMap和HashSet没有Object.hashCode()支持?

时间:2012-06-22 04:21:33

标签: java hashmap hashset lookup-tables

我正在尝试编写一个服务器,该服务器使用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方法完全无法工作,因为我打算它);我该如何解决这个问题?

3 个答案:

答案 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 是动态的,取决于它自身的可变特性。

希望有所帮助