4

Android 面试官装逼失败之:equals 和 hashCode 的区别

 3 years ago
source link: https://zhuanlan.zhihu.com/p/37333670
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android 面试官装逼失败之:equals 和 hashCode 的区别

斯拉夫传承者,嘴炮学家

前两天看到这篇 Android 面试官装逼失败之:Activity 的启动模式 默默笑了,想想自己面试别人的时候也经常会问一些自己都不是很熟的问题,如果对面反问我就 GG 了(事实上这种事还真发生过几次,作为「石板房最强 Android 工程师」的有力竞争者,感觉自己面子上都挂不住)。不过这个主题只有一篇文章,感觉可以写成一个系列。

不过我倒是无意像大多数技术博客那样由浅入深由表及里地分析相关的知识点,更多还是倾向于「当我作为一个面试官问到这个问题的时候,我更希望听到什么」(潜台词:我也不想为了写篇文章就去刨根问底呀,虽然这不是严谨的治学态度)。同时还会分享一些自己在面试别人的过程中发生的有意思的事。作为这个系列的开篇,先来写篇简单的。


一个月前为了做新项目,搬工位到了贵司某后端组旁边。正好该后端组正在做 Python 向 Java 的迁移工作,于是听到了如下对话(基于回忆略有偏差,强行还原情节):

- 你这代码写得不对啊,不能用 hashCode 而应该用 equals
- 可是在这里 hashCode 应该能判断这两个对象相等了吧?
- 那你说说 equals 和 hashCode 的区别是什么?
- 这个,嗯...

作为 Android 开发的我默默笑了,一道经典的面试题就这样在实战出现了。之所以会有这样的问题,我想很大程度上是因为 hash 给人(我)的概念(印象)往往都与「唯一性」挂钩。不过在讨论 hashCode 之前,我们先来说说 equals ,附上一份官方文档 Object#equals ,同时我尝试用自己的语言翻译一下。

equals 顾名思义,就是判断两个对象是否为同一个对象。对于非空( non-null )对象而言,equals 都具有如下性质:

  • 它是自反的,例如 x.equals(x) 总是返回 true
  • 具有对称性,即 x.equals(y) 总是与 y.equals(x) 的返回值相同
  • 具有传递性,若 x.equals(y) 返回 truey.equals(z) 返回 true ,则 x.equals(z) 返回 true
  • 行为总是一致的,就是说无论调用多少次 x.equals(y) 其返回值总是不变,前提是 equals 中用于判断两个对象是否相等的值没有发生变化(如果这些值发生了变化,那么最终的返回值有可能发生变化)
  • x.equals(null) 总是应该返回 false

equals 的基本实现(即 Object.class 中的实现)是直接使用 == 操作符来实现:

  • 对于 Java 的基本类型(如 int/float )来说,直接判断是否相等
  • 如果是两个对象,则对比他俩的内存地址是否相同

注意,通常来说重写 equals 时也要重写 hashCode ,并且对于 equals 返回 true 的两个对象而言,他们的 hashCode 返回值务必相等(终于写到 hashCode 了)。

而 hashCode(官方文档 Object#hashCode )通常用于哈希表的实现中,例如 HashMap.

总的来说 hashCode 的实现需要满足以下几点:

  • 对于同一个对象而言,不管调用几次该对象的 hashCode 方法,其返回值都应该是一样的,前提是该对象中用于生成 hashCode 的值没有发生任何变化。文档中说的是用于判断 equals 的值没有发生任何变化,不过我没有 get 到其中的点,难道用于生成 hashCode 的值一定要包含在 equals 里吗?F**k! I got it, because equals return true => hashCode 必须相等,这一规则隐含了「用于判断 equals 的的值必定包含了用于生成 hashCode 的值」的条件
  • 并没有强制要求说两个不同的对象,其 hashCode 一定不同,也就是说存在 hash 冲突 的可能性(于是这里可以简单引申出 如何解决 hash 冲突 的问题)。不过为了减少解决 hash 冲突的开销,还是建议对于不同的对象尽可能返回不同的 hashCode

我们在写 Model(即 MVC/MVP/MVVM 中的 Model ,通常都是 JSON 对象及其封装啦)的过程中难免要重写 equals/hashCode 方法,虽然使用 AS 直接 Command + N 可以直接选择生成,但相关的概念还是不应该忽视掉。如果面试者能把上述概念讲得比较清楚(并不需要把 equals 的那一堆特性讲得清楚),并且能说上一两个 hash 冲突的方法(如「拉链法」),那么我作为面试官就会觉得比较 OK.

PS: 如果 x.equals(y) == truex.hashCode() != y.hashCode() 会发生什么情况呢?简单来说以 HashMap#containsKey 为例,其中的一个关键方法参见代码块中的中文注释:

/**
 * Implements Map.get and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @return the node, or null if none
 */
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // 首先对比 hash 值,如果不等就返回 null ;如下同理
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

所以如果你非要把 hashCode 搞成不相等的,那么 HashMap#containsKey 就会给你返回 false ,除非你自己同时重新写一个 HashMap 实现支持这种特性。It's by design :P


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK