Java HashSet 包含返回 false,即使重写了 equals() 和 hashCode()
Posted
技术标签:
【中文标题】Java HashSet 包含返回 false,即使重写了 equals() 和 hashCode()【英文标题】:Java HashSet contains returns false, even with overridden equals() and hashCode() 【发布时间】:2015-09-18 17:10:43 【问题描述】:我这样初始化 HashSet:
private HashSet<Rule> ruleTable = new HashSet<Rule>();
我的TcpRule
对象(抽象类Rule
的子类)的equals()
和hashCode()
方法如下所示:
@Override
public int hashCode()
// Ignore source Port for now
return (this.getSrcPool() + ":" + this.getDstPool() + ":" + this.getProtocol() + ":" + this.dstTcp).hashCode();
@Override
public boolean equals(Object obj)
if (!(obj instanceof TcpRule))
return false;
if (obj == this)
return true;
TcpRule r = (TcpRule) obj;
return (this.getSrcPool().equals(r.getSrcPool()) && this.getDstPool().equals(r.getDstPool()) && this.getProtocol().equals(r.getProtocol()) && this.getSrcTcp() == r.getSrcTcp() && this.getDstTcp() == r.getDstTcp());
我什至写了一个简单的单元测试,没有任何错误:
@Test
public void equalsTest()
Pool srcPool = new Pool("PROXY");
Pool dstPool = new Pool("WEB");
int srcTcp = 54321;
int dstTcp = 80;
TcpRule r1 = new TcpRule(srcPool, dstPool, srcTcp, dstTcp);
TcpRule r2 = r1;
assert r1.equals(r2);
TcpRule r3 = new TcpRule(srcPool, dstPool, srcTcp, dstTcp);
TcpRule r4 = new TcpRule(srcPool, dstPool, srcTcp, dstTcp);
assert r3.equals(r4);
@Test
public void hashCodeTest()
Pool srcPool = new Pool("PROXY");
Pool dstPool = new Pool("WEB");
int srcTcp = 54321;
int dstTcp = 80;
TcpRule r1 = new TcpRule(srcPool, dstPool, srcTcp, dstTcp);
TcpRule r2 = new TcpRule(srcPool, dstPool, srcTcp, dstTcp);
assert r1.hashCode() == r2.hashCode();
HashSet<Rule> rules = new HashSet<Rule>();
rules.add(r1);
assert rules.contains(r1);
assert rules.contains(r2);
在我的应用程序中,我有一个add()
方法,我只需将Rule
对象添加到HashSet
:
@Override
public void add(Rule rule)
ruleTable.add(rule);
在另一种方法中,我检查HashSet
中是否存在规则:
@Override
public boolean isPermittedTcp(IpAddress sourceAddress, IpAddress destinationAddress, short srcTcp, short dstTcp)
Pool sourcePool = poolService.getPool(new Host(sourceAddress));
Pool destinationPool = poolService.getPool(new Host(destinationAddress));
Rule r = new TcpRule(sourcePool, destinationPool, srcTcp, dstTcp);
log.info("Checking: " + r.toString());
log.info("Hash-Code: " + r.hashCode());
log.info("Hashes in ruleTable:");
for(Rule rT : ruleTable)
log.info("" + rT.hashCode());
if(ruleTable.contains(r))
log.info("Hash found!");
else
log.info("Hash not found!");
return ruleTable.contains(r);
日志消息表明Rule
对象(r.hashCode()
)的哈希是-1313430269
,而HashSet
(循环中的rT.hashCode()
)中的一个哈希也是-1313430269
。
但是ruleTable.contains(r)
总是返回false
。我做错了什么?
我在 *** 上发现了类似的问题,但这些问题主要涉及 equals()
或 hashCode()
方法未被(正确)覆盖。我想我已经正确地实现了这两种方法。
【问题讨论】:
还有 rT.equals(r) 吗? 您的测试使用了相同的 Pool 实例,您确定 equals 在 Pool 上运行良好吗? 你在 equals this.getSrcTcp() == r.getSrcTcp() 中有一个额外的条件,它不是哈希码的一部分 - 也许这就是问题所在,哈希码是相同的,但 equals 是假的跨度> 如果您发布一个简短但完整的示例来演示问题,那将非常有帮助... 顺便说一句,一旦你弄清楚你的问题,你可能想修改你如何实现你的hashCode()
方法。连接字符串来计算哈希码比它需要的更昂贵。阅读here 了解其他想法。
【参考方案1】:
有一些可能性:
Rule
是可变的,在向集合添加规则后,某些键(w.r.t. hash 或 equals)字段已更改;
如果两个对象相等,它们应该具有相同的 hashCode;
错误,就像在equals
中使用==
i.o 进行比较。 equals
。
我猜你有两个 Pool
实例,在池名称上没有等于或在池名称上没有 hashCode。
【讨论】:
【参考方案2】:您的问题是 hashCode()
和 equals()
确实不同意。
您的hashCode()
实现基于池的toString()
,但您的equals()
使用池类的.equals()
。
更改您的.equals()
以比较用于生成哈希码的字符串。
【讨论】:
【参考方案3】:您在 equals this.getSrcTcp() == r.getSrcTcp() 中有一个额外的条件,它不是哈希码的一部分 - 也许这就是问题所在,哈希码是相同的,但 equals 是错误的。检查此字段在您比较的值中是否不同。
尽管有 cmets,但我认为这不起作用的原因是 equals 和 hashCode 实现不使用相同的字段。
模拟问题的代码:
import java.util.HashSet;
/**
* @author u332046
*
*/
public class HashCodeCollisionTest
public static class KeyDemo
String id;
@Override
public int hashCode()
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
@Override
public boolean equals(Object obj)
/*if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
KeyDemo other = (KeyDemo) obj;
if (id == null)
if (other.id != null)
return false;
else if (!id.equals(other.id))
return false;
return true;*/
return false;
public KeyDemo(String id)
super();
this.id = id;
static HashSet<KeyDemo> set = new HashSet<>();
public static void main(String[] args)
set.add(new KeyDemo("hi"));
set.add(new KeyDemo("hello"));
System.out.println(set.contains(new KeyDemo("hi")));
这会打印出false
。取消注释等于代码并打印true
【讨论】:
你还没有证明你认为你已经证明了。您的equals
方法总是 返回false
,所以显然它永远不会打印“true”。更好的测试是取消注释 equals
中的所有代码,但让 hashCode()
返回 0 - 所以 all 实例的值相同。
另请阅读Object.hashCode
的文档:“根据equals(java.lang.Object) 方法,如果两个对象不相等,则不要求,然后调用两个对象中每一个的 hashCode 方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象产生不同的整数结果可能会提高哈希表的性能。"
我试图说明两者必须一致。 HashMap.getEntry 确实检查相等 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
我觉得这很有趣,因为你是对的,也是错的。你错了,equals
和 hashCode
都需要根据完全相同的字段列表进行计算(@Jon Skeet 像往常一样 100% 正确)。然而,很可能你是对的,不同的领域是导致 OP 难以置信地挠头的原因。但这不会是因为HashSet
做错了什么。这可能是因为 OP 做出了错误的假设,即具有相等的 hashCodes
自动意味着 HashSet.Contains()
检查将返回 true。
@6ton:假设您有一个领域,其中计算一个像样的哈希码可能非常昂贵,但相等检查通常很便宜 - 并且实例很少仅不同场地。在这种情况下,不将其包含在哈希码中是有意义的。以上是关于Java HashSet 包含返回 false,即使重写了 equals() 和 hashCode()的主要内容,如果未能解决你的问题,请参考以下文章