【问题标题】:Weird behavior of java.util.Set.contains(Object o)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

【问题讨论】:

    标签: java set equals contains


    【解决方案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(),因此您需要覆盖两者。 HereHashSet#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 的建议。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-10
    • 1970-01-01
    • 1970-01-01
    • 2015-04-12
    • 1970-01-01
    • 1970-01-01
    • 2021-06-08
    相关资源
    最近更新 更多