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。如果不遵守此不变量,则HashMapHashSetHashtable 将无法正常工作。

hashcode()equals(Object) 的行为方式的技术细节在 Object API 中指定。


那么,如果您弄错了,为什么基于散列的数据结构会损坏呢?好吧,基本上是因为哈希表通过使用哈希函数的值来缩小要与“候选”进行比较的值集。如果候选对象的哈希码与表中某些对象的哈希码不同,那么查找算法可能不会与表中的对象进行比较……即使对象相等。

【讨论】:

那么在我的情况下,您如何看待返回 month * 100 + dayhashCode() 它应该可以正常工作。出于技术原因,将100 替换为素数(例如 31)会稍微好一些。但是,这不会有太大的不同。【参考方案2】:

HashSet 将只使用equals() 如果 元素共享相同的hashCode(),因此您需要覆盖两者。 Here 是HashSet#contains() 使用的代码的相关部分(注意HashSetHashMap 支持):

  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 + dayhashCode() 是的,它唯一标识每个 MonthAndDay 对象。然后HashMap(和HashSet)将重新散列您提供的hashCode(),以尝试确保足够好的分发。请查看此处 (linuxtopia.org/online_books/programming_books/thinking_in_java/…),了解 Joshua Block 对实施 hashCodes 的建议。

以上是关于java.util.Set.contains(Object o) 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

php中 ob_函数 例:ob_start();用法

OB3635/OB2530PAP/OB3398昂宝电子设计

实现静态化

php #ob_start #ob_clean #ob_end_clean

php函数ob_start()ob_end_clean()ob_get_contents()

php关于ob_start('ob_gzhandler')启用GZIP压缩的bug