1

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

导入java.lang.*;
导入java.io.*;
导入 java.util.*;

类客户 ID {
    私有字符串 id;

    公共客户端 ID(字符串 myId)
    {
        身份证=我的身份证;
    }

    公共静态 ClientID generateNew(Set<ClientID> 存在)
    {
        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<ClientID> mySet = new HashSet<ClientID>();
        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 方法根本不像我想要的那样工作它到);我该如何解决这个问题?

4

3 回答 3

4

在 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);
}
于 2012-06-22T04:27:01.430 回答
3

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。

于 2012-06-22T04:31:56.483 回答
0

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

你应该小心任何通过哈希码获取其子级的 Java 集合,因为它的子类型的哈希码取决于它的可变状态。一个例子:

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

HashSet通过它的hashCode检索它的item,但是它的item类型是一个HashSet,hashSet.hashCode取决于它的items state。

与此相关的代码:

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 是动态的并且取决于其自身的可变属性。

希望有帮助

于 2015-02-28T21:51:12.173 回答