java.util.Set.contains(Object o) 的奇怪行为
Posted
技术标签:
【中文标题】java.util.Set.contains(Object o) 的奇怪行为【英文标题】:Weird behavior of java.util.Set.contains(Object o) 【发布时间】:2012-08-28 06:35:21 【问题描述】:关于java.util.Set.contains(Object o)
的doc 说:
当且仅当此集合包含元素 e 时返回 true,使得 (o==null ? e==null : o.equals(e))。
也就是说,这是一个 POJO(如您所见,我覆盖了它的 equals
方法):
public class MonthAndDay
private int month;
private int day;
public MonthAndDay(int month, int day)
this.month = month;
this.day = day;
@Override
public boolean equals(Object obj)
MonthAndDay monthAndDay = (MonthAndDay) obj;
return monthAndDay.month == month && monthAndDay.day == day;
请问,为什么下面的代码打印的是false
而不是true
?
Set<MonthAndDay> set = new HashSet<MonthAndDay>();
set.add(new MonthAndDay(5, 1));
System.out.println(set.contains(new MonthAndDay(5, 1)));
// prints false
一个解决方案是重写contains(Object o)
方法,但原来的应该(几乎)完全一样,我错了吗?
Set<MonthAndDay> set = new HashSet<MonthAndDay>()
private static final long serialVersionUID = 1L;
@Override
public boolean contains(Object obj)
MonthAndDay monthAndDay = (MonthAndDay) obj;
for (MonthAndDay mad : this)
if (mad.equals(monthAndDay))
return true;
return false;
;
set.add(new MonthAndDay(5, 1));
System.out.println(set.contains(new MonthAndDay(5, 1)));
// prints true
【问题讨论】:
【参考方案1】:当您覆盖equals(Object)
时,您还需要覆盖hashcode()
。
具体来说,必须实现方法,使得如果a.equals(b)
是true
,那么a.hashcode() == b.hashcode()
就是所有true
。如果不遵守此不变量,则HashMap
、HashSet
和Hashtable
将无法正常工作。
hashcode()
和 equals(Object)
的行为方式的技术细节在 Object API 中指定。
那么,如果您弄错了,为什么基于散列的数据结构会损坏呢?好吧,基本上是因为哈希表通过使用哈希函数的值来缩小要与“候选”进行比较的值集。如果候选对象的哈希码与表中某些对象的哈希码不同,那么查找算法可能不会与表中的对象进行比较……即使对象相等。
【讨论】:
那么在我的情况下,您如何看待返回month * 100 + day
的 hashCode()
?
它应该可以正常工作。出于技术原因,将100
替换为素数(例如 31)会稍微好一些。但是,这不会有太大的不同。【参考方案2】:
HashSet
将只使用equals()
如果 元素共享相同的hashCode()
,因此您需要覆盖两者。 Here 是HashSet#contains()
使用的代码的相关部分(注意HashSet
由HashMap
支持):
355 /**
356 * Returns the entry associated with the specified key in the
357 * HashMap. Returns null if the HashMap contains no mapping
358 * for the key.
359 */
360 final Entry<K,V> getEntry(Object key)
361 int hash = (key == null) ? 0 : hash(key.hashCode());
362 for (Entry<K,V> e = table[indexFor(hash, table.length)];
363 e != null;
364 e = e.next)
365 Object k;
366 if (e.hash == hash &&
367 ((k = e.key) == key || (key != null && key.equals(k))))
368 return e;
369
370 return null;
371
不这样做,违反Object#hashCode()
的合同,其中规定:
如果两个对象根据
equals(Object)
方法相等,那么 对两个对象中的每一个调用hashCode
方法必须产生 相同的整数结果。
【讨论】:
其实HashSet使用equals()
来比较具有相同hashCode()
的元素
@PeterLawrey:你说得对,当你讨论类似的事情时,我实际上正在阅读你的博客文章:)。我已经编辑了我的答案。
那么在我的情况下,您如何看待返回 month * 100 + day
的 hashCode()
?
是的,它唯一标识每个 MonthAndDay
对象。然后HashMap
(和HashSet
)将重新散列您提供的hashCode()
,以尝试确保足够好的分发。请查看此处 (linuxtopia.org/online_books/programming_books/thinking_in_java/…),了解 Joshua Block 对实施 hashCode
s 的建议。以上是关于java.util.Set.contains(Object o) 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章
php #ob_start #ob_clean #ob_end_clean